Spring 에서 비동기 주해@Async 의 사용,원리 및 사용 시 발생 할 수 있 는 문제 및 해결 방법
사실 최근 에 업무 와 관련 된 내용 을 연구 하고 있 습 니 다.이런 글 을 쓴 이 유 는 앞에서 순환 의존 에 관 한 글 을 썼 기 때 문 입 니 다.
《 Spring 순환 의존 해결 방법,정말 알 겠 어? 》
그 다음 에 많은 학생 들 이 다음 과 같은 문 제 를 만 났 습 니 다.Spring 이 제공 하 는 비동기 주 해 를 추가 하여
@Async
순환 의존 이 해결 되 지 않 았 습 니 다.다음은 일부 독자 들 의 댓 글 과 단체 친구 들 이 만 나 는 문제 입 니 다.지식 을 말 하려 면 명확 하고 투철 한 원칙 을 말 해 야 한다.나 는 이런 글 을 단독으로 써 서
@Async
이라는 주 해 를 상세 하 게 소개 하기 로 결정 했다.이 주해 가 가 져 온 문 제 는 순환 의존 이 이렇게 간단 한 것 이 아니 라 익숙 하지 않 으 면 신중하게 사용 하기 로 했다.문장의 요점
@Async 의 기본 사용
이 주해 의 역할 은 표 시 된 방법 을 비동기 적 으로 집행 할 수 있 지만 두 가지 전제조건 이 있다.
설정 클래스 에
@EnableAsync
주 해 를 추가 하여 비동기 실행 이 필요 한 방법 이 있 는 종 류 는 Spring 관리 에 비동기 실행 이 필요 한 방법 에 @Async
주 해 를 추가 하 였 습 니 다.저희 가 데모 하나 로 이 주해 의 역할 을 알 아 보도 록 하 겠 습 니 다.
첫 번 째,설정 클래스 에서 비동기 시작:
@EnableAsync
@Configuration
@ComponentScan("com.dmz.spring.async")
public class Config {
}
두 번 째 단계,[code]
@Component // Spring public class DmzAsyncService { @Async //
@Component // Spring
public class DmzAsyncService {
@Async //
public void testAsync(){
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("testAsync invoked");
}
}
세 번 째 단계,비동기 실행 테스트
public class Main {
public static void main(String[] args) {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(Config.class);
DmzAsyncService bean = ac.getBean(DmzAsyncService.class);
bean.testAsync();
System.out.println("main ");
}
}
// :
// main
// testAsync invoked
위의 예 를 통 해 우 리 는 DmzAsyncService
중의 testAsync
방법 이 비동기 적 으로 집행 되 었 다 는 것 을 알 수 있다.그러면 이 뒤의 원 리 는 무엇 일 까?이어서 분석 하 겠 습 니 다.원리 분석
우리 가 어떤 기술 을 분석 할 때 가장 중요 한 것 은 반드시 코드 의 입 구 를 찾 아야 한 다 는 것 이다.Spring 과 같은 것 은 모두 뚜렷 하 다.입 구 는 반드시
@EnableAsync
이라는 주해 위 에 있 을 것 이다.우 리 는 이 주해 가 무슨 일 을 했 는 지 살 펴 보 자(본 고 는 5.2.x
판 을 바탕 으로)
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
// , ImportSelector
@Import(AsyncConfigurationSelector.class)
public @interface EnableAsync {
// , @Async
Class<? extends Annotation> annotation() default Annotation.class;
// jdk
boolean proxyTargetClass() default false;
// Spring AOP
AdviceMode mode() default AdviceMode.PROXY;
// ,
// AsyncAnnotationBeanPostProcessor, Ordered
// AsyncAnnotationBeanPostProcessor
int order() default Ordered.LOWEST_PRECEDENCE;
}
위의 이 주해 에서 가장 중요 한 일 은 바로 AsyncConfigurationSelector
을 가 져 온 것 이다.이런 종류의 소스 코드 는 다음 과 같다.
public class AsyncConfigurationSelector extends AdviceModeImportSelector<EnableAsync> {
private static final String ASYNC_EXECUTION_ASPECT_CONFIGURATION_CLASS_NAME =
"org.springframework.scheduling.aspectj.AspectJAsyncConfiguration";
@Override
@Nullable
public String[] selectImports(AdviceMode adviceMode) {
switch (adviceMode) {
// SpringAOP
case PROXY:
return new String[] {ProxyAsyncConfiguration.class.getName()};
case ASPECTJ:
return new String[] {ASYNC_EXECUTION_ASPECT_CONFIGURATION_CLASS_NAME};
default:
return null;
}
}
}
이러한 역할 은 용기 에 ProxyAsyncConfiguration
을 등록 한 것 과 같은 것 으로 이런 계승 관 계 는 다음 과 같다.우 리 는 먼저 그것 의 아버지 류
AbstractAsyncConfiguration
을 보 았 는데 그 소스 코드 는 다음 과 같다.
@Configuration
public abstract class AbstractAsyncConfiguration implements ImportAware {
@Nullable
protected AnnotationAttributes enableAsync;
@Nullable
protected Supplier<Executor> executor;
@Nullable
protected Supplier<AsyncUncaughtExceptionHandler> exceptionHandler;
// EnableAsync
//
@Override
public void setImportMetadata(AnnotationMetadata importMetadata) {
this.enableAsync = AnnotationAttributes.fromMap(
importMetadata.getAnnotationAttributes(EnableAsync.class.getName(), false));
if (this.enableAsync == null) {
throw new IllegalArgumentException(
"@EnableAsync is not present on importing class " + importMetadata.getClassName());
}
}
// AsyncConfigurer
// ,
//
@Autowired(required = false)
void setConfigurers(Collection<AsyncConfigurer> configurers) {
if (CollectionUtils.isEmpty(configurers)) {
return;
}
if (configurers.size() > 1) {
throw new IllegalStateException("Only one AsyncConfigurer may exist");
}
AsyncConfigurer configurer = configurers.iterator().next();
this.executor = configurer::getAsyncExecutor;
this.exceptionHandler = configurer::getAsyncUncaughtExceptionHandler;
}
}
ProxyAsyncConfiguration
같은 소스 코드 를 다시 보 겠 습 니 다.
@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() {
AsyncAnnotationBeanPostProcessor bpp = new AsyncAnnotationBeanPostProcessor();
// AsyncConfigurer
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;
}
}
이 클래스 자 체 는 하나의 설정 클래스 로 용기 에 AsyncAnnotationBeanPostProcessor
을 추가 하 는 역할 을 합 니 다.이 단계 에 이 르 러 우 리 는 기본적으로 알 수 있 습 니 다.@Async
주 해 는 AsyncAnnotationBeanPostProcessor
이라는 백 엔 드 프로 세 서 를 통 해 하나의 에이전트 대상 을 생 성하 여 이 보 를 실현 하 는 것 입 니 다.그 다음 에 AsyncAnnotationBeanPostProcessor
이 어떻게 에이전트 대상 을 생 성 하 는 지 구체 적 으로 살 펴 보 겠 습 니 다.우 리 는 주로 몇 가 지 를 주목 하면 됩 니 다.라 이 프 사이클 의 어느 단계 에 완 성 된 대리 입 니까?
우 리 는 중점 을 잡 았 습 니 다.
AsyncAnnotationBeanPostProcessor
은 백 엔 드 프로세서 입 니 다.우리 가 Spring 에 대해 알 고 있 는 바 에 따 르 면 대략 이 백 엔 드 프로세서 의 postProcessAfterInitialization
방법 에서 대 리 를 완 성 했 습 니 다.이 방법 은 부모 류 AbstractAdvisingBeanPostProcessor
에 위치 하고 구체 적 인 코드 는 다음 과 같 습 니 다.
public Object postProcessAfterInitialization(Object bean, String beanName) {
// , AOP ,
if (this.advisor == null || bean instanceof AopInfrastructureBean) {
return bean;
}
// , ,
// beforeExistingAdvisors
// @Async ,beforeExistingAdvisors true
//
if (bean instanceof Advised) {
Advised advised = (Advised) bean;
if (!advised.isFrozen() && isEligible(AopUtils.getTargetClass(bean))) {
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());
}
return bean;
}
아니 나 다 를 까,확실히 이 방법 에서 완 성 된 대리 다.이어서 우 리 는 절 점 의 여과 규칙 이 무엇 인지 생각해 야 한다.접점 의 논 리 는 어 떻 습 니까?
사실 클래스 에
@Async
주 해 를 추가 하거나 클래스 에 @Async
주 해 를 수식 하 는 방법 이 들 어 있 음 을 짐작 하기 어렵 지 않다.이 를 바탕 으로 우 리 는 이 isEligible
방법의 실현 논 리 를 살 펴 보 자.이 방 위 는 AbstractBeanFactoryAwareAdvisingPostProcessor
에 있 고 AsyncAnnotationBeanPostProcessor
의 부류 이기 도 하 며 대응 코드 는 다음 과 같다.
// AbstractBeanFactoryAwareAdvisingPostProcessor isEligible
//
protected boolean isEligible(Object bean, String beanName) {
return (!AutoProxyUtils.isOriginalInstance(beanName, bean.getClass()) &&
super.isEligible(bean, beanName));
}
protected boolean isEligible(Object bean, String beanName) {
return isEligible(bean.getClass());
}
protected boolean isEligible(Class<?> targetClass) {
Boolean eligible = this.eligibleBeans.get(targetClass);
if (eligible != null) {
return eligible;
}
if (this.advisor == null) {
return false;
}
//
eligible = AopUtils.canApply(this.advisor, targetClass);
this.eligibleBeans.put(targetClass, eligible);
return eligible;
}
실제로 마지막 으로 advisor 에 따라 대 리 를 진행 할 지 여 부 를 확인 하 는 것 입 니 다.Spring 에서 xml 기반 AOP 의 상세 한 절차 이 글 에서 우 리 는 advisor 가 실제 적 으로 절 점 을 연결 한 통지 라 고 언급 한 적 이 있 습 니 다.그러면 AsyncAnnotationBeanPostProcessor
이 advisor 는 언제 초기 화 되 었 습 니까?우 리 는 AsyncAnnotationBeanPostProcessor
의 setBeanFactory
방법 을 직접 찾 았 는데 그 소스 코드 는 다음 과 같다.
public void setBeanFactory(BeanFactory beanFactory) {
super.setBeanFactory(beanFactory);
// new AsyncAnnotationAdvisor
AsyncAnnotationAdvisor advisor = new AsyncAnnotationAdvisor(this.executor, this.exceptionHandler);
if (this.asyncAnnotationType != null) {
advisor.setAsyncAnnotationType(this.asyncAnnotationType);
}
advisor.setBeanFactory(beanFactory);
//
this.advisor = advisor;
}
AsyncAnnotationAdvisor
의 절 점 일치 규정 이 어떤 지 살 펴 보 자.이런 유형의 buildPointcut
방법 에서 그 소스 코드 는 다음 과 같다.
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);
}
코드 는 간단 합 니 다.cpc 와 mpc 두 개의 일치 기 에 따라 일치 합 니 다.첫 번 째 는 클래스 에@Async 주해 가 있 는 지,두 번 째 는@Async 주해 가 있 는 지 확인 하 는 것 입 니 다.그러면 지금까지 우 리 는 그것 이 언제 대 리 를 만 들 었 는 지,왜 대상 이 대 리 를 만 들 었 는 지 알 게 되 었 습 니 다.마지막 으로 우 리 는 문 제 를 해결 해 야 합 니 다.대리 의 논 리 는 어 떻 습 니까?비동기 가 어떻게 실현 되 었 습 니까?
통지 의 논 리 는 어 떻 습 니까?어떻게 비동기 적 인 것 을 실현 합 니까?
앞에서 도 advisor 는 절 점 을 연결 한 알림 이 라 고 언급 했 습 니 다.앞에서 절 점 을 분 석 했 습 니 다.그러면 지금 우 리 는 그의 통지 논 리 를 살 펴 보고
AsyncAnnotationAdvisor
중의 buildAdvice
방법 을 직접 찾 았 습 니 다.소스 코드 는 다음 과 같 습 니 다.
protected Advice buildAdvice(
@Nullable Supplier<Executor> executor, @Nullable Supplier<AsyncUncaughtExceptionHandler> exceptionHandler) {
AnnotationAsyncExecutionInterceptor interceptor = new AnnotationAsyncExecutionInterceptor(null);
interceptor.configure(executor, exceptionHandler);
return interceptor;
}
간단 하 죠.차단 기 를 추 가 했 을 뿐 입 니 다.interceptor 유형의 대상 에 대해 우 리 는 그것 의 핵심 방법 인 invoke
에 관심 을 가지 면 됩 니 다.코드 는 다음 과 같 습 니 다.
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();
}
}
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());
}
초래 된 문제 및 해결 방안문제 1:순환 의존 오류
이 그림 에서 이 독자 가 묻 는 질문 처럼
두 가지 로 나 누 어 대답 하 다.
첫째:순환 의존 은 왜 해결 되 지 않 습 니까?
이 문 제 는 사실 매우 간단 하 다.라 는 글 에서 나 는 두 가지 측면 에서 순환 의존 하 는 처리 절 차 를 분석 했다.
단순 대상 간 순환 의존 처리 AOP 대상 간 순환 의존 처리
이런 사고방식 에 따 르 면
@Async
주해 로 인 한 순환 의존 은 AOP
에 속 하고 처 리 될 수 있어 야 한다.그러나 중요 한 것 은 AOP 대상 간 순환 의존 을 해결 하 는 핵심 방법 은 3 급 캐 시 입 니 다.다음 과 같 습 니 다.3 급 캐 시 에 공장 대상 을 캐 시 했 습 니 다.이 공장 대상 은
getEarlyBeanReference
방법 으로 초기 대리 대상 의 인용 을 얻 을 것 입 니 다.그 소스 코드 는 다음 과 같 습 니 다.
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
Object exposedObject = bean;
if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
for (BeanPostProcessor bp : getBeanPostProcessors()) {
// , @EnableAsync
// AsyncAnnotationBeanPostProcessor SmartInstantiationAwareBeanPostProcessor
// AsyncAnnotationBeanPostProcessor
// Bean
if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
}
}
}
return exposedObject;
}
위의 코드 순환 의존 문 제 를 보면 분명 하 다.초기 에 노출 된 대상 이 최종 적 으로 용기 에 넣 은 대상 과 같 지 않 기 때문에 잘못 보고 했다.잘못 보고 한 구체 적 인 위 치 를 나 는 Spring 의 순환 의존 도 를 말씀 드 리 겠 습 니 다. 문장 말미 에 이미 분 석 했 으 니 본 고 는 더 이상 군말 하지 않 겠 다.해결 방안
위 독자 가 제시 한 데모 의 경우 B 에 A 를 주입 할 때
@Lazy
주 해 를 하나 추가 하면 된다
@Component
public class B implements BService {
@Autowired
@Lazy
private A a;
public void doSomething() {
}
}
이 주해 의 역할 은 B 에 A 를 주입 할 때 A 에 게 대리 대상 을 만들어 B 에 주입 하 는 것 이다.대리 대상 을 진정 으로 호출 하 는 방법 이 있 을 때 바 텀 은 getBean(a)
을 호출 하여 A 대상 을 만 든 다음 에 방법 을 호출 하 는 것 이다.이 주해 의 처리 시 기 는 org.springframework.beans.factory.support.DefaultListableBeanFactory#resolveDependency
방법 에서 이 주 해 를 처리 하 는 코드 는 org.springframework.context.annotation.ContextAnnotationAutowireCandidateResolver#buildLazyResolutionProxy
에 있다.이 코드 들 은 사실 제 가 전에 쓴 글 에서 다 분 석 했 어 요.《 Spring Bean 의 라 이 프 사이클 에 대한 이 해 를 말씀 드 리 겠 습 니 다. 》
《 Spring 잡담|Spring 의 Autowire Candidate Resolver
그래서 본 고 는 더 이상 상세 한 분석 을 하지 않 는 다.
문제 2:기본 스 레 드 탱크 는 스 레 드 를 다시 사용 하지 않 습 니 다.
나 는 이것 이 이 주해 가 가장 깊 은 곳 이 라 고 생각한다.하나 도 없다!우 리 는 그것 이 기본적으로 사용 하 는 스 레 드 탱크 가 무엇 인지 살 펴 보 았 다.앞의 소스 코드 분석 에서 우 리 는 스 레 드 탱크 를 사용 하기 로 결정 하 는 방법 이
org.springframework.aop.interceptor.AsyncExecutionAspectSupport#determineAsyncExecutor
이라는 것 을 알 수 있다.그 소스 코드 는 다음 과 같다.
protected AsyncTaskExecutor determineAsyncExecutor(Method method) {
AsyncTaskExecutor executor = this.executors.get(method);
if (executor == null) {
Executor targetExecutor;
// @Async
String qualifier = getExecutorQualifier(method);
if (StringUtils.hasLength(qualifier)) {
targetExecutor = findQualifiedExecutor(this.beanFactory, qualifier);
}
else {
//
targetExecutor = this.defaultExecutor.get();
}
if (targetExecutor == null) {
return null;
}
executor = (targetExecutor instanceof AsyncListenableTaskExecutor ?
(AsyncListenableTaskExecutor) targetExecutor : new TaskExecutorAdapter(targetExecutor));
this.executors.put(method, executor);
}
return executor;
}
최종 적 으로 org.springframework.aop.interceptor.AsyncExecutionInterceptor#getDefaultExecutor
이라는 방법 으로 호출 될 겁 니 다.
protected Executor getDefaultExecutor(@Nullable BeanFactory beanFactory) {
Executor defaultExecutor = super.getDefaultExecutor(beanFactory);
return (defaultExecutor != null ? defaultExecutor : new SimpleAsyncTaskExecutor());
}
기본적으로 사용 하 는 스 레 드 풀 은 SimpleAsyncTaskExecutor
임 을 알 수 있 습 니 다.우 리 는 이러한 소스 코드 를 보지 않 고 위의 문서 주석 만 봅 니 다.다음 과 같 습 니 다.주로 세 가 지 를 얘 기 했 어 요.
OOM
을 드 리 겠 습 니 다.해결 방안
가장 좋 은 방법 은 사용자 정의 스 레 드 탱크 를 사용 하 는 것 입 니 다.주로 몇 가지 설정 방법 이 있 습 니 다.
이전의 소스 코드 분석 에서 우 리 는
AsyncConfigurer
을 통 해 사용 하 는 스 레 드 풀 을 설정 할 수 있다 는 것 을 알 수 있다.다음 과 같다.
public class DmzAsyncConfigurer implements AsyncConfigurer {
@Override
public Executor getAsyncExecutor() {
//
}
}
@Async 주석 에 사용 할 스 레 드 탱크 의 이름 을 직접 설정 합 니 다.다음 과 같다.
public class A implements AService {
private B b;
@Autowired
public void setB(B b) {
System.out.println(b);
this.b = b;
}
@Async("dmzExecutor")
public void doSomething() {
}
}
@EnableAsync
@Configuration
@ComponentScan("com.dmz.spring.async")
@Aspect
public class Config {
@Bean("dmzExecutor")
public Executor executor(){
//
return executor;
}
}
총결산본 고 는 주로 Spring 에서 비동기 주해 의 사용,원리 와 부 딪 힐 수 있 는 문 제 를 소개 하고 모든 문제 에 대해 서도 방안 을 제시 했다.이 글 을 통 해
@Async
주해 의 사용 을 철저히 파악 하고 그 이 유 를 알 수 있 기 를 바 랍 니 다!Spring 의 비동기 주해@Async 의 사용,원리 및 사용 시 발생 할 수 있 는 문제 및 해결 방법 에 관 한 이 글 은 여기까지 소개 합 니 다.Spring 비동기 주해@Async 사용 원리 에 관 한 더 많은 내용 은 이전 글 을 검색 하거나 아래 의 관련 글 을 계속 찾 아 보 세 요.앞으로 도 많은 응원 부 탁 드 리 겠 습 니 다!
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
[MeU] Hashtag 기능 개발➡️ 기존 Tag 테이블에 존재하지 않는 해시태그라면 Tag , tagPostMapping 테이블에 모두 추가 ➡️ 기존에 존재하는 해시태그라면, tagPostMapping 테이블에만 추가 이후에 개발할 태그 기반 ...
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.