Home AOP with java
Post
Cancel

AOP with java

AOP란??

AOP는 Aspect Oriented Programming으로 관점 지향 프로그래밍으로, Cross cutting concern(횡단 관심사)의 분리를 허용하여 모듈성을 증가시키는 것이 목적인 프로그래밍 방법론, 패러다임이다.
위키백과

Cross cutting concern (횡단 관심 모듈)

서비스의 비즈니스 로직을 포함하는 기능을 core concern(핵심 기능)이라고 하고, 주요 비즈니스 로직을 도와주는 기능을 cross cutting concern(부가 기능)이라고 한다. 부가 기능의 예시로는 로깅, 보안, 트랜잭션이 있다.

어플리케이션 로깅

예를 들어 메서드의 앞 뒤로 로그를 찍어야 한다고 가정한다. 현재 달아야 할 메서드는 5개밖에 없다. 수작업으로 로그를 찍는 코드를 추가할 수 있다. 이 작업은 금방 끝난다.

로그를 달아야 할 메서드가 10만개라고 생각해보자. 손으로 하나씩 다는건 상상할 수 없는 일이다. 또한 메서드가 5개일 때 주요 로직에 로깅 코드를 달았던 것도 고칠 필요가 있다. 이 때 AOP를 사용해 로그를 찍을 수 있다. 간단한 java 코드로 aop의 동작을 알아보자.

Aop를 사용하기 위해 reflect.proxy를 사용해 구현할 수 있다. 한 학생의 과목 점수의 합과 평균을 구하는데 걸리는 시간을 로그로 확인해보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class AopExam implements Exam {
  private int kor;
  private int eng;
  private int math;

	...

  @Override
  public int total() {
    return kor + eng + math;
  }

  @Override
  public float avg() {
    // 시간 체크할 aop가 들어갈 예정
    return total() / 3f;
  }
}

AopExam 클래스를 생성하고 Exam 인터페이스를 상속 받는다. 인터페이스에 totalavg 함수가 선언 되어 있고 이를 AopExam에서 구현했다.

1
2
3
4
5
6
7
8
@Override
public int total() {
  long start = System.currentTimeMillis();
  int total = kor+ eng+ math;
  long end = System.currentTimeMillis();

  return total;
}
1
2
3
4
5
6
7
public class Program {

  public static void main(String[] args) {
    Exam exam = new AopExam(10, 20, 30);
    System.out.println("total : " + exam.total());
  }
}

AOP를 적용하기 전 코드이다. 전체 과목의 합을 구하는 메서드에서 합을 계산하는 시간을 로그에 찍기 위해 메서드 앞 뒤로 시간을 체크해서 걸린 시간을 확인한다. 로그는 잘 찍히지만 메서드가 많아지면 관리하기 어려워진다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public class Program {

  public static void main(String[] args) {
		Exam exam = new AopExam(10, 20, 30); // Exam만 호출하면exam 주요 로직만 호출
    // interfaces는 배열로 들어갈 수 있다 -> 인터페이스를 가지고 proxy를 생성할 수 있다
    Exam proxyInstance =
        (Exam)
            Proxy.newProxyInstance(
                AopExam.class.getClassLoader(),
                new Class[] {Exam.class},
                new InvocationHandler() {
                  @Override
                  public Object invoke(Object proxy, Method method, Object[] args)
                      throws Throwable {
                    // 부가 기능
                    long start = System.currentTimeMillis();

                    // 핵심 기능
                    //        method.invoke(obj, args)
                    Object result = method.invoke(exam, args);
                    // 핵심 기능 끝

                    // 부가 기능
                    Thread.sleep(1000);
                    long end = System.currentTimeMillis();

                    String message = (end - start) + "ms 시간이 걸렸습니다.";
                    System.out.println(message);

                    return result;
                  }
                }); // 부가 기능을 사용해서 Exam을 호출
    System.out.println("total is " + proxyInstance.total());
    System.out.println("avg is " + proxyInstance.avg());
  }
}

proxy를 사용해 aop를 구현해보았다. Proxy instance는 loader, interfaces, h를 파라미터로 갖는다.

  • loader

proxy 클래스를 정의하는 class loader이다.

  • interfaces

구현할 프록시 클래스의 인터페이스 목록

  • h

메소드 호출을 전달하기 위한 호출 핸들러

AopExam 클래스 로더와 AopExam가 상속한 Exam 인터페이스를 클래스 배열에 추가해준다. InvokationHandler를 사용해 부가기능을 정의한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public Object invoke(Object obj, Object... args)
    throws IllegalAccessException, IllegalArgumentException,
       InvocationTargetException
{
    if (!override) {
        Class<?> caller = Reflection.getCallerClass();
        checkAccess(caller, clazz,
                    Modifier.isStatic(modifiers) ? null : obj.getClass(),
                    modifiers);
    }
    MethodAccessor ma = methodAccessor;             // read volatile
    if (ma == null) {
        ma = acquireMethodAccessor();
    }
    return ma.invoke(obj, args);
}

InvokationHandlerinvoke 메서드를 이용해 핵심 기능을 실행한다. invoke 메서드는 obj와 args를 파라미터로 받게된다. obj는 사용될 메서드가 호출되는 객체이고 args는 호출되는 메서드에 필요한 값들이다. invoke 메서드는 리플랙션을 사용해 클래스의 필드와 메서드를 호출한다. 리플랙션은 클래스의 접근 제어자와 상관없이 해당 클래스의 모든 필드와 메서드를 호출할 수 있다.

다시 위 코드로 돌아가면 invoke 메서드 앞과 뒤로 원하는 부가 기능을 추가하면 된다. 과목의 합과 평균을 구하는 메서드의 실행 시간을 알기위해 현재 시간을 찍어준다. proxy instance는 객체 최상위 클래스인 Object를 반환하기 때문에 클래스 캐스팅을 해줘야 한다.

1
2
3
4
1005ms 시간이 걸렸습니다.
total is 60
1006ms 시간이 걸렸습니다.
avg is 20.0

proxyInstance를 호출하면 AopExam 클래스의 total과 avg 메서드를 호출할 수 있다. proxy를 사용해 핵심 기능에 로깅 코드를 추가하지 않고 로그를 확인할 수 있게 되었다.

This post is licensed under CC BY 4.0 by the author.

[Effective Java] Generic에 대하여…

Vite 사용해보기