[Spring] AOP(Aspect Oriented Programming)

JANG SEONG SU·2023년 8월 11일
0

Sping

목록 보기
3/9
post-thumbnail
post-custom-banner

AOP(Aspect Oriented Programming)

AOP는 관점 지향 프로그래밍이다. 관점을 지향한다는 것이 대체 무슨 뜻인지 잘 와닿지 않는다.
여기서 관점이란 어떤 기능을 구현할 때, 그 기능을 핵심 기능부가 기능으로 구분해 각각을 하나의 관점으로 보는 것을 의미한다.

이것 역시 잘 와닿지 않을 수 있다. 예시를 들어 더 자세히 알아보겠다.

Calculator.java

public interface Calculator{
    public long factorial(long num);
}

다음과 같이 팩토리얼을 계산하는 Calculator 인터페이스가 있다.

BasicCalculator.java

public class BasicCalculator implements Calculator {
    @Override
    public long factorial(long num) {
        long result = 0;
        for(long i = 1;i<num;i++){
            result *= i;
        }
        return result;
    }
}

Caculator 인터페이스를 구현받아, For-loop로 팩토리얼을 계산하는 BasicCalculator

이때 만약, BasicCalculator에서 구현한 factorial()의 실행 시간을 구하는 요구사항이 추가되면 다음과 같이 구현할 수 있다.

public class BasicCalculator implements Calculator {
    @Override
    public long factorial(long num) {
        long start = System.currentTimeMills();
        try {
        	long result = 0;
        	for(long i = 1;i<num;i++){
            result *= i;
        	}
        	return result;
        } finally {
        	long end = System.currentTimeMills();
	        System.out.printf("BasicCalculator 실행 시간 = %d\n", (end - start));
    	}
	}
}

하지만 BasicCalculator 이외에 다른 100개 이상의 Calculator 구현체에 실행 시간을 구하는 요구사항들이 추가된다면, 코드의 유지보수는 끔찍하게 나빠질 것이다.

심지어 실행 시간을 구하는 로직은 비즈니스 로직이 아닌, 부가 기능일 뿐인데 그것에 시간을 쏟는 것은 최악의 경우다.

이를 해결할 수 있는 방법이 프록시 패턴(Proxy Pattern)이다. 네트워크에서 사용하는 Proxy server에서의 그 Proxy와 동일한 개념이다.

최종적으로는 나누어 구현한 부가기능과 핵심기능을 하나로 동작하는 Proxy 객체를 생성하는 것이다. 이에 대한 내용은 앞으로 나올 예정이니깐 이 정도만 알고 있으면 된다.

프록시 패턴 예제

그럼 프록시 패턴을 적용하여 코드를 변경해 보겠다.

public class ExecutionTimeCalculator implements Calculator {

	private Calculator delegate;
    
    public ExecutionTimeCalculator(final Calculator delegate) {
    	this.delegate = delegate;
    }
    
    @Override
    public long factorial(long num) {
        long start = System.currentTimeMills();
        long result = delegate.factorinal(num);
        long end = System.currentTimeMills();
        System.out.printf("%s 실행 시간 = %d\n", delegate.getClass().getSimpleName(), (end - start));
        return result;
	}
}

만약 아래와 같은 재귀를 이용한 RecurCalculator가 추가되어도 RecurCalculator 클래스를 변경하지 않아도 기능을 구현할 수 있다.

RecurCalculator.java

public class RecurCalculator implements Calculator {
    @Override
    public long factorial(long num) {
            if(num == 0)
                return 1;
            else
                return num*factorial(num-1);
    }
}

바로 이렇게 말이다.

Calculator proxyCalculator1 = new ExecutionTimeCalculator(new BasicCalculator());
System.out.println(proxyCalculator1.factorial(20));

Calculator proxyCalculator2 = new ExecutionTimeCalculator(new BasicCalculator());
System.out.println(proxyCalculator2.factorial(20));
  • 네트워크 프록시 서버

  • AOP 프록시 패턴

그림과 같이 ExecutionTimeCalculator는 네트워크의 프록시와 같은 역할을 하게 되는 것이다.
이로 인해, 기존 코드를 변경하지 않고 실행 시간을 출력할 수 있고, 실행 시간을 구하는 코드 중복도 제거된다.

정리하자면, 개발자는 여러 객체에서 공통으로 적용할 수 있는 부가 기능을 분리하여 반복 작업을 줄이고, 핵심 기능 개발에만 집중할 수 있다.


AOP 관련 용어

Aspect

AOP의 기본 모듈. Advice(부가 기능의 내용) + Pointcut(부가 기능을 어디에 둘지), 즉 부가 기능을 구현한 클래스에 Advice를 적용하도록 지원할 수 있는 것을 Aspect

Target

부가 기능을 부여할 대상 객체

Advice

부가 기능의 순수한 내용을 의미. Before,After,Around의 실행 위치 지정

JoinPoint

Advice(부가 기능이)이 적용될 수 있는 지점

PointCut

Advice(부가 기능)를 어느 JointPoint에 둘지 선별하는 모듈

📌 AOP 3가지 방법
1. 컴파일 시점에 코드에 공통 기능 삽입 ⬅ AspectJ
2. 클래스 로딩 시점에 바이트 코드에 공통 기능 삽입 ⬅ AspectJ
3. 런타임 시점에 프록시 객체를 생성하여 공통 기능 삽입 ✔

Spring에서는 3번을 사용


📖AOP 사용 예시

우선 Spring AOP는 Bean으로 등록된 객체에 대해서만 프록시 객체를 생성할 수 있다.

  • proceed(): 실제 대상 객체의 메서드를 호출
  • Signature getSignature(): 호출한 메서드의 시그너처(메서드 이름과 파라미터를 뜻함), 정보를 구한다.
  • Object getTarget(): 호출한 메서드의 대상 객체를 구한다.
  • Object[] getArgs(): 호출한 메서드의 인자 목록을 구한다.

Advice 설정

동작시점설명
Before메소드 실행 전 동작
After메소드 실행 후 동작
After-returning메소드가 Exception없이 실행되면 실행 후 동작
After-throwingException발생하면, 발생한 후 동작
Around메소드 호출 이전, 이후, 예외발생 등 모든 시점에서 동작

TestCode

실행 결과를 보면, BasicCalculator 뒤에 "$$ 블라블라!@!@!!!"가 붙어있다.
이는 사실, BasicCulator.factorial()을 실행한 것이 아닌 BasicCalulator의 프록시 객체인 BasicCalculator$$!@#!#가 실행된 것이다.

ExecutionTimeAspectTestBasicCalculator 사이에 프록시 객체가 있다.

프록시 객체는 BasicCalulator를 감싸고 있다고 생각하면 된다.


profile
Software Developer Lv.0
post-custom-banner

0개의 댓글