본문 바로가기

Java/@Transaction 구현

5. 애너테이션 적용

@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