@Transactional 구현 - 목차
2021.12.02 - [Java/@Transaction 구현] - 1. 배경설명, 구현 동기 및 목표
2021.12.02 - [Java/@Transaction 구현] - 2. Aspect Oriented Programming
2021.12.02 - [Java/@Transaction 구현] - 3. Proxy 패턴
2021.12.02 - [Java/@Transaction 구현] - 4. JDK Dynamic Proxy
2021.12.02 - [Java/@Transaction 구현] - 5. 애너테이션 적용 (현재 글)
앞에서는 메서드의 이름으로 쓰기 트랜잭션 진입 여부를 결정했다. 메소드의 이름으로 부가기능 적용여부를 결정하면, 부가기능을 동적으로 적용할 수 없다. 부가기능을 동적으로 적용하기 위해서는 다른 방법으로 적용대상을 구별할 필요가 있다.
@Transactional 작성
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Target(ElementType.METHOD)
public @interface Transactional {
}
Annotation | 설명 |
@Retention(RetentionPolicy.RUNTIME) | 실행시에도 애너테이션을 유지한다. |
@Inherited | 부모클래스에 선언된 애너테이션을 자식 클래스에서도 사용할 수 있다. |
@Target(ElementType.METHOD) | 메소드에만 애너테이션을 적용할 수 있다. |
@Transactional 찾기
RealCell.java
public class RealCell implements Cell{
...
@Override
@Transactional
public void setValue(Object value) {
System.out.println("real setValue() : " + value);
}
}
setValue() 메서드 위에 앞서 만든 @Transactional 애너테이션을 붙인다.
이제 RealCell 에서 애너테이션 존재 여부를 확인해야 한다. 메서드의 시그니처 위에 작성한 애너테이션은 리플렉션을 이용해서 찾을 수 있다.
@DisplayName("@Transactional 애너테이션을 찾는다.")
@Test
void findAnnotation () {
RealCell cell = new RealCell();
Method[] methods = cell.getClass().getMethods();
for(Method m : methods){
if(m.isAnnotationPresent(Transactional.class)){
System.out.println(
m.getName() + ", annotation : " + m.getAnnotation(Transactional.class)
);
}
}
}
RealCell 객체에 선언된 모든 메소드정보를 가져와서 메소드에 애너테이션이 붙었는지 확인한다. 위 테스트 코드를 실행하면 아래와 같이 출력된다.
setValue, annotation : @proxy.jdkdynamic.annotation.cell.Transactional()
에너테이션이 붙어있는 메서도를 찾는 방법을 확인했다. 이제 부가기능 적용여부를 애너테이션으로 판단해 적용할 수 있다.
AnnotationInvocationHandler.java
public class AnnotationInvocationHandler implements InvocationHandler {
private final Cell realCell;
public AnnotationInvocationHandler(Cell realCell) {
this.realCell = realCell;
}
@Override
public Object invoke(Object proxy, Method proxyMethod, Object[] args) throws Throwable {
System.out.println("=== Authentication ===");
Method[] realMethods = realCell.getClass().getMethods();
Transactional transactional = null;
for(Method realMethod : realMethods){
if(proxyMethod.getName().equals(realMethod.getName()) &&
realMethod.isAnnotationPresent(Transactional.class))
{
transactional = realMethod.getAnnotation(Transactional.class);
}
}
if(transactional == null){
return proxyMethod.invoke(realCell, args);
}
System.out.println("=== Transaction in ===");
Object result = proxyMethod.invoke(realCell, args);
System.out.println("=== Transaction out ===");
return result;
}
}
invoke() 의 두 번째 파라미터인 proxyMethod
는 프록시 객체가 인터페이스에 따라 호출하는 메서드이다.
현재 프록시가 호출한 메소드의 이름과 RealCell의 메소드 이름을 비교한다. 그리고 이름이 동일한 RealCell 의 메소드에 애너테이션이 붙어있는지 확인한다.
애너테이션이 붙어있지 않다면, 쓰기 트랜잭션에 진입하지 않고 proxyMethod.invoke(realCell, args)
메소드로 canEdit(), getValue() 메소드를 실행한다. @Transactional
이 붙어있다면, 쓰기 트랜잭션에 진입한 다음 proxyMethod.invoke()
를 실행한다.
실행결과

애너테이션을 적용하기 전과 동일한 결과가 나온다.
애너테이션을 사용할 때 장점
어떤 부가기능이 실행되는지 알 수 있다.
- 메소드의 이름으로 부가기능 적용여부를 결정했을 떈, 어떤 이유로 부가기능이 추가되는지 알기 어렵다.
- 애너테이션의 이름을 적절히 설정하면, 애너테이션만으로도 현재 메서드에 어떤 부가기능이 추가될 지 예상할 수 있다.
동적으로 부가기능을 적용할 수 있다.
- 앞에서는 setValue() 에 대해서만 쓰기 트랜잭션 내에서 실행되도록 설정했다. getValue(), canEdit() 에도 @Transactional 을 붙이면, 쓰기 트랜잭션 내에서 실행되도록 할 수 있다.
@Override
@Transactional
public Object getValue() {
System.out.println("real getValue()\n");
return null;
}

부가기능을 쉽게 추가할 수 있다.
- 애너테이션을 추가하고, 애너테이션에 대응하는 부가기능을 작성해 InvocationHandler에 작성하면, 애터네이션을 추가하는 것 만으로도 부가기능을 추가할 수 있다.
마치며
지금까지 스프링 프레임워크의 @Transactional
을 모방해 사내 프레임워크에서 사용할 @Transactional
애너테이션 제작과정을 살펴보았다.
메소드마다 개발자가 직접 작성하는 핵심기능과 핵심기능을 실행하기 전, 후 에 실행하는 부가기능이 있다. 메소드마다 핵심기능 주변에 모양이 비슷한 부가기능을 실행하는 코드를 넣다보면, 중복코드가 많이 발생한다.
이런 중복코드를 줄이기위해, 관점 지향 프로그래밍(AOP)에 대해 알아보았고, 프록시 패턴을 이용해 AOP 를 달성했다.
부가기능을 동적으로 적용하기위해 애너테이션을 사용했다. 메소드 시그니처 상단에 애너테이션을 추가함으로서 부가기능을 수행할 수 있게되었고, 애너테이션을 추가하는것 만으로 부가기능을 실행할 수 있었다.
'Java > @Transaction 구현' 카테고리의 다른 글
4. JDK Dynamic Proxy (0) | 2021.12.02 |
---|---|
3. Proxy 패턴 (0) | 2021.12.02 |
2. Aspect Oriented Programming (0) | 2021.12.02 |
1. 배경설명, 구현 동기 및 목표 (0) | 2021.12.02 |