You can make anything
by writing

C.S.Lewis

by anonymDev Jun 05. 2022

@카지노 쿠폰al은 어떻게 만들어졌을까?

소스 코드로 이해하는 @카지노 쿠폰al

spring-framework 5.3.x 기준으로 작성됐으며 모든 소스코드의 출처는 spring-framework입니다(출처).


@카지노 쿠폰al어노테이션을클래스, 메서드에붙이기만하면카지노 쿠폰보장된메서드가실행된다. 코드에트랜잭션의시작과종료를호출하는코드를작성할필요가없다. spring-data-jpa과 함께사용할경우에데이터베이스의트랜잭션시작과커밋(또는 롤백)이 알아서 된다.


<@카지노 쿠폰al 사용 예제 코드\

@Service

public class카지노 쿠폰alService {
@카지노 쿠폰al
public voiddoMultiTasks(String name) {

doTask1();

doTask2();
}
}

@카지노 쿠폰al 어노테이션 하나로 트랜잭션 관련 로직을 신경 쓰지 않고 비즈니스 로직에 집중할 수 있는 장점이 있다. 대충 어떠할 것이다라고는 알고 있지만 구체적으로 어떻게 동작하는지는 몰랐다. 이번 기회에코드를 직접 보며 함께 알아보자.


@카지노 쿠폰al이 붙은 메서드는 어떻게 실행될까?


비밀은프락시(Proxy)에있다. @카지노 쿠폰al을포함하는빈클래스카지노 쿠폰alService단순히new로 생성되는 게 아니라 프락시로감싸져생성된다. 프락시로생성하는이유는메서드호출을가로채앞또는뒤(또는앞뒤모두)로추가 로직을실행하기위해서다.

혹시 프락시의 개념을 잘 모른다면 자바의 DynamicProxy에 대해서 검색해보자


doMultiTasks() 메서드가 호출되는 시점부터 코드를 보자. 트랜잭션 메서드를 가로채는 클래스는 MethodInterceptor의 구현체 카지노 쿠폰Interceptor이다. 프락시로 생성된 카지노 쿠폰alService의 메서드 호출을 가로채 invokeWithin카지노 쿠폰을 호출함으로써 호출된 메서드가 트랜잭션 안에서 실행되도록 한다.


<카지노 쿠폰Interceptor.java

public class카지노 쿠폰Interceptor extends카지노 쿠폰AspectSupport implementsMethodInterceptor, Serializable {

@Override
@Nullable

//MethodInvocation의 Method는 doMultitasks()이다.
publicObject invoke(MethodInvocation invocation) throws Throwable {
...

/*MethodInvocation을 트랜잭션 안에서 실행하는 invokeWithin카지노 쿠폰를 호출한다.

*invocation: doMultiTasks()

*targetClass: 카지노 쿠폰alService

*/
returninvokeWithin카지노 쿠폰(invocation.getMethod(), targetClass, new CoroutinesInvocationCallback() {
@Override
@Nullable
public Object proceedWithInvocation() throws Throwable {
return invocation.proceed();
}
...
}


상위 클래스 카지노 쿠폰AspectSupport에 정의돼있는 invokeWithin카지노 쿠폰주석을 참고하며 읽어 내려가 보자.


<카지노 쿠폰AspectSupport.java

@Nullable
protectedObject invokeWithin카지노 쿠폰(Method method, @Nullable Class <? targetClass,
final InvocationCallback invocation) throws Throwable {

...

//메소드와 클래스로부터 트랜잭션 속성을 가져온다.

final카지노 쿠폰Attribute txAttr = (tas != null? tas.get카지노 쿠폰Attribute(method, targetClass) : null);

/* (1) 트랜잭션을 생성하고 시작한다.

* 단 조건부로 트랜잭션 정보가 있는 경우에만 트랜잭션을 시작한다. 이름도 ...ifNecessary이다.(참고) */

카지노 쿠폰Info txInfo = create카지노 쿠폰IfNecessary(ptm, txAttr, joinpointIdentification);


Object retVal;
try {
// This is an around advice: Invoke the next interceptor in the chain.
// This will normally result in a target object being invoked.
retVal = invocation.proceedWithInvocation(); // (2) doMultiTasks()가 실행된다.
}
catch (Throwable ex) {
// target invocation exception
complete카지노 쿠폰AfterThrowing(txInfo, ex); // (3-1) 익셉션이 발생한 경우 rollback이 호출된다
throw ex;
}
finally {
cleanup카지노 쿠폰Info(txInfo);
}

...
commit카지노 쿠폰AfterReturning(txInfo); //(3-2) 트랜잭션을 커밋한다.
returnretVal;

}


doMultiTasks호출(2)되기 전에 트랜잭션을 시작이 시작된다(1). 만약doMultiTasks가 익셉션을 던진다면 catch하여 롤백 한다(3-1). 정상적으로 종료됐다면 3-2에서 트랜잭션을 커밋하고 결과 값을 반환하는 것을 확인할 수 있다.


프락시로 한번 감싸진카지노 쿠폰alService의 메소드doMultiTasks가 호출되면 트랜잭션 인터셉터(카지노 쿠폰alInterceptor)가로채 트랜잭션 관련 로직을 처리해준 것이다. 이 때 모든 메소드 호출에서 트랜잭션 로직이 실행되는게 아니라 잠깐 언급했듯이 조건부로 실행된다.doMultiTasks의 경우 @카지노 쿠폰al 어노테이션을 붙임으로써 조건에 부합한 셈이다(조건:txAtrr != null)


그렇다면 카지노 쿠폰alService은 어떻게 프락시 빈이될까?


프락시 빈 생성에 앞서어드바이저(Advisor)에 대해서 간단하고 알고 넘어가자. 어드바이저는 AOP의 advice와 pointcut 정보를 갖고 있는 클래스다.@카지노 쿠폰al에도 AOP 개념이활용됐다. 쉽게 말해서 '어떤 클래스의 어떤 조건을 가진 메소드를 실행할 때 무슨 액션을 어느 지점에서 실행할지' 알려주는 게 어드바이저이다.


빈팩토리가 카지노 쿠폰Service를 빈으로 만들때 트랜잭션프록시로 생성하는데어드바이저 역할을 하는 클래스가 BeanFactory카지노 쿠폰AttributeSourceAdvisor이다.이 클래스는 spring-tx모듈에서 빈으로 정의돼 로드된다.


<Proxy카지노 쿠폰ManagementConfiguration.java

@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public classProxy카지노 쿠폰ManagementConfiguration extendsAbstract카지노 쿠폰ManagementConfiguration {

/*트랜잭션 어드바이저로 BeanFactory카지노 쿠폰AttributeSourceAdvisor를 등록했다.

*파라미터로 카지노 쿠폰AttributeSource카지노 쿠폰Interceptor를 주입받는 것을 확인할 수 있다.

*두 개의 빈은 어드바이저 아래에 선언돼있다.*/

@Bean(name = 카지노 쿠폰ManagementConfigUtils.카지노 쿠폰_ADVISOR_BEAN_NAME)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
publicBeanFactory카지노 쿠폰AttributeSourceAdvisor 카지노 쿠폰Advisor(
카지노 쿠폰AttributeSource 카지노 쿠폰AttributeSource,

카지노 쿠폰Interceptor 카지노 쿠폰Interceptor) {

BeanFactory카지노 쿠폰AttributeSourceAdvisor advisor = newBeanFactory카지노 쿠폰AttributeSourceAdvisor();
advisor.set카지노 쿠폰AttributeSource(카지노 쿠폰AttributeSource);
advisor.setAdvice(카지노 쿠폰Interceptor);
if (this.enableTx != null) {
advisor.setOrder(this.enableTx.<IntegergetNumber("order"));
}
returnadvisor;
}


/*카지노 쿠폰AttributeSource의 구현체로 Annotation카지노 쿠폰AttributeSource를 주입받았다.

*카지노 쿠폰AttributeSource는 트랜잭션 속성을 어디서/어떻게 가져올지 알고 있는 클래스이다.

*Annotation카지노 쿠폰AttributeSource은 그 속성들을 어노테이션으로부터 읽어오도록 구현됐다.

*ex) @카지노 쿠폰al*/

@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public카지노 쿠폰AttributeSource 카지노 쿠폰AttributeSource() {
// Accept protected @카지노 쿠폰al methods on CGLIB proxies, as of 6.0.
return new Annotation카지노 쿠폰AttributeSource(false);
}

/* 위에서 doMultiTasks의 트랜잭션을 처리했던 카지노 쿠폰Interceptor가 여기서 빈으로 생성된다.

* 그리고 BeanFactory카지노 쿠폰AttributeSourceAdvisor의 어드바이스(Advice)로 넘겨진다.*/

@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public카지노 쿠폰Interceptor 카지노 쿠폰Interceptor(카지노 쿠폰AttributeSource 카지노 쿠폰AttributeSource) {
카지노 쿠폰Interceptor interceptor = new카지노 쿠폰Interceptor();


//인터셉터에도 Annotation카지노 쿠폰AttributeSource을 주입해준다.

interceptor.set카지노 쿠폰AttributeSource(카지노 쿠폰AttributeSource);
if(this.txManager != null) {
interceptor.set카지노 쿠폰Manager(this.txManager);
}
returninterceptor;
}

...

}


BeanFactory카지노 쿠폰AttributeSourceAdvisor빈 생성 단계에서 빈 인스턴스 후처리 과정에서 활용된다.


<AbstractAutowireCapableBeanFactory.java

@Override
publicObject applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName) throws BeansException {
Object result = existingBean;
for (BeanPostProcessor processor : getBeanPostProcessors()) {

//초기화된 빈을 파라미터로 넘겨 후 처리한다.
Object current = processor.postProcessAfterInitialization(result, beanName);

....(중략)
returnresult;
}


BeanPostProcessor 중에 빈 클래스를 프락시로 감싸주는 AbstractAutoProxyCreator구현체가 있다. 빈에 매칭 되는 어드바이저를 찾아 빈 클래스의 프락시 인스턴스에 어드바이스로 주입한다.


public abstract classAbstractAutoProxyCreator extends ProxyProcessorSupport
implements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware {


@Override
publicObject postProcessAfterInitialization(@Nullable Object bean, String beanName) {
if(bean != null) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
if(this.earlyProxyReferences.remove(cacheKey) != bean) {

//카지노 쿠폰Service를 프록시로 감싼다
returnwrapIfNecessary(bean, beanName, cacheKey);
}
}
returnbean;
}


카지노 쿠폰Service의 프록시를 생성하기 전에 카지노 쿠폰Service에 매칭되는 어드바이스와 어드바이저를 가져온다.

@Override
publicObject wrapIfNecessary(Class<? beanClass, String beanName) {
...

// Create proxy if we have advice.
Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);

... 중략

}


카지노 쿠폰Service의 어드바이저를 찾아서 반환하는데 어드바이저가 없는 경우 DO_NOT_PROXY를 반환한다.

@Override
protectedObject[] getAdvicesAndAdvisorsForBean(
Class<? beanClass, String beanName, @Nullable TargetSource targetSource) {

//카지노 쿠폰alService(beanClass)에 해당하는 Advisor를 찾는다.
List<Advisor advisors = findEligibleAdvisors(beanClass, beanName);
if(advisors.isEmpty()) {
return DO_NOT_PROXY; //advisor가 없는 경우 '프락시 하지 않는다'를 반환한다
}
returnadvisors.toArray(); //Proxy 인스턴스에 활용될 advisor(인터셉터)를 반환한다
}

...


한 단계 더 들어가 보자. 모든 어드바이저를 반환하는게 아니라 후보가 되는 어드바이저를 가져온 후에 그중에서 카지노 쿠폰Service에게eligible(적합한) 어드바이저를 골라낸다.

protectedList<Advisor findEligibleAdvisors(Class<? beanClass, String beanName) {

//findEligibleAdvisor를 살펴보면 후보가 되는 candidateAdvisors에서

// beanClass에 적용할 수 있는 eligibleAdvisor를 솎아낸다.
List<Advisor candidateAdvisors = findCandidateAdvisors();
List<Advisor eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);
...(중략)
returneligibleAdvisors;
}


이렇게 타고 들어가다 보면 eligible한 어드바이저인지 여부를AopUtils.canApply으로판별하는 것을 확인할 수 있다. 주석을 보며 읽어내려가보자.


/*PointCut은 BeanFactory카지노 쿠폰AttributeSourceAdvisor이 가진

* 카지노 쿠폰AttributeSourcePointcut가 넘겨진다.*/

public staticboolean canApply(Pointcut pc, Class<? targetClass, boolean hasIntroductions) {

Assert.notNull(pc, "Pointcut must not be null");


/*PointCut(카지노 쿠폰AttributeSourcePointcut)의 classFitler로 카지노 쿠폰alService가 *PointCut에 매칭 되는지 필터링한다.

*내부적으로 Annotation카지노 쿠폰AttributeSource의 isCandidateClass()가 호출된다(소스코드).*/

if(!pc.getClassFilter().matches(targetClass)) {
return false;
}

...(중략)

//PointCut의 MethodMatcher를 가져온다

MethodMatcher methodMatcher = pc.getMethodMatcher();

...(중략)


//카지노 쿠폰alService의 모든 부모와 인터페이스를 뽑아낸다.
Set<Class<? classes = new LinkedHashSet<();
if (!Proxy.isProxyClass(targetClass)) {
classes.add(ClassUtils.getUserClass(targetClass));
}
classes.addAll(ClassUtils.getAllInterfacesForClassAsSet(targetClass));

/*인터페이스와 클래스들의 메서드를 순회하며 BeanFactory카지노 쿠폰AttributeSourceAdvisor

*와 PointCut과 매칭여부를 확인 후 매칭 하는 경우 true를 반환한다.*/

for(Class<? clazz : classes) {
Method[] methods = ReflectionUtils.getAllDeclaredMethods(clazz);
for (Method method : methods) {
if(introductionAwareMethodMatcher != null ?
introductionAwareMethodMatcher.matches(method, targetClass, hasIntroductions) :

methodMatcher.matches(method, targetClass)) { <-- methodMather의 matches 실행

//카지노 쿠폰AttributeSourcePointcut과매칭 되는 메서드가 있으면 true를 반환한다.

returntrue;
}
}
}

...(중략)
}


BeanFactory카지노 쿠폰AttributeSourceAdvisor의 MethodMatcher는 카지노 쿠폰AttributeSourcePointcut이다.


<카지노 쿠폰AttributeSourcePointcut.class

@Override
public booleanmatches(Method method, Class<? targetClass) {

//카지노 쿠폰Attribute로 Annotation카지노 쿠폰AttributeSource를 가져온다.

카지노 쿠폰AttributeSource tas = get카지노 쿠폰AttributeSource();

// 타깃이 되는 메서드에서 트랜잭션 어트리뷰트가 존재하는 경우 True를 반환한다.
return (tas == null || tas.get카지노 쿠폰Attribute(method, targetClass) != null);
}


트랜잭션 관련 속성이 있는 빈 클래스라면 true를 반환한다. 카지노 쿠폰AttributeSource의 구현체로 Annotation카지노 쿠폰AttributeSource가 주입이 됐던 게 기억나는가? Annotation카지노 쿠폰AttributeSource은 @카지노 쿠폰al 어노테이션에서 속성 값을 가져온다. doMultiTasks()는 @카지노 쿠폰al 어노테이션이 붙어 있으므로속성 값이 null이 아니므로 matches()가 true를 반환한다.


<Annotation카지노 쿠폰AttributeSource.java

@Nullable

protected카지노 쿠폰Attribute determine카지노 쿠폰Attribute(AnnotatedElement element) {

//트랜잭션 어노테이션 파서들을 순회하며 엘리먼트(메서드 또는 클래스)를 파싱 한다
for(카지노 쿠폰AnnotationParser parser : this.annotationParsers) {
카지노 쿠폰Attribute attr = parser.parse카지노 쿠폰Annotation(element);

//파싱 된 속성이 존재하는 경우 해당 속성 값(카지노 쿠폰Attribute)을 반환한다.

if(attr != null) {
returnattr;
}
}
returnnull;
}


카지노 쿠폰Service의 advisor로BeanFactory카지노 쿠폰AttributeSourceAdvisor를 가져왔으니다시 BeanPostProcessor로 돌아가서 빈과 어드바이저로 프록시를 생성하자.

<AbstractAutoProxyCreator.java

@Override

publicObject wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
...

//specificInterceptors에 담긴 BeanFactory카지노 쿠폰AttributeSourceAdvisor가 포함된다.

Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);

//DO_NOT_PROXY를 반환하지 않았으므로 프락시를 생성한다.
if (specificInterceptors != DO_NOT_PROXY) {
this.advisedBeans.put(cacheKey, Boolean.TRUE);

//어드바이저를 파라미터로 넘겨 프락시 인스턴스를 생성한다.

Object proxy = createProxy(
bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
this.proxyTypes.put(cacheKey, proxy.getClass());
return proxy;
}
//매칭되는 어드바이저가 없는빈의 경우 그대로 반환한다.
this.advisedBeans.put(cacheKey, Boolean.FALSE);
return bean;

}


정리하면

@카지노 쿠폰이 붙은 메서드가 있는 빈 클래스는 빈 초기화 후에 BeanPostProcessor를 통해서 트랜잭션을 지원하는 프락시로 감싸진다. 따라서 해당 메소드가 실행될 때 트랜잭션 관련 로직이 앞뒤로 실행된다.


1. BeanPostProcessor가 카지노 쿠폰Service를 프락시로 감싼다(AbstractAdvisorAutoProxyCreator)

2. 트랜잭션의 어드바이저는 BeanFactory카지노 쿠폰AttributeSourceAdvisor이다

3. Annotation카지노 쿠폰AttributeSource가 @카지노 쿠폰al을 파싱 하고 속성 정보를 읽는다

4. 카지노 쿠폰Interceptor가 @카지노 쿠폰 메서드 호출을 가로채 트랜잭션 관련 로직을 처리한다.





브런치는 최신 브라우저에 최적화 되어있습니다.