스펙타클한 주말이었다.
물론 나는 아니지만 카카오 서버가 죽어버리는 일이 생겼다…













화재로 인해 전기를 내려서 서버가 죽었고
장애가 생겨 거의 하루종일 서비스 이용이 안되고
어떤사람은 전동킥보드를 빌렸는데 서비스장애로
돈이 10만원 넘게 청구되었단다 ㅋㅋㅋㅋ

뭔가 남일 같지만은 않은…
나중에 내가 어떤회사의 서비스를 맡고 저런일이 생긴다면
정말 힘든 나날이 될 것 같다…


여하튼 오늘은 AOP에 대해 공부하는 날이다.
AOP는 이론은 정말 간단 한거 같은데
어떻게 스프링을 이용해 적용하는지가 가장 궁금하다.
공부를 시작해보자

AOP

Aspect Oriented Programming의 약자이다.
이전에도 알아보았지만 관심지향 프로그래밍으로
모듈화의 핵심 단위를 관점으로 작성하는 프로그래밍이다.
OOP방식의 불필요한 반복을 해결하기 위한 방법이다.

간단하게 생각하자면
한가지의 기능을 여러가지 메소드에 쓰고싶을때
같은 코드를 여러번 작성하면 코드 길이가 길어지고
추후 수정이 필요할때 전부 수정하는 번거로움이 있기 때문에
하나의 관심기능으로 묶어 기존의 코드를 손대지 않고
그 기능들을 적용해서 사용할 수 있는 방식을 얘기하는 것이다.

  1. 핵심 관심 사항 (core concern)
    -. 객체가 제공하는 고유의 기능이다. (업무 로직 등..)

  2. 공통 관심 사항 (cross-cutting concern)
    -. 핵심 기능을 보조하기 위해 제공되는 기능이다.
    -. 주로 로그 추적로직, 트랜잭션, 보안 기능 등이 있다.

AOP 기술을 사용하기전에 용어에 익숙해지기위해
관련 용어에 대한 내용을 알아보자













Aspect
-. 여러 객체에 공통으로 적용되는 기능

Pointcut
-. JoinPoint 중에서 Advice가 적용될 위치를 선별하는 기능
-. 관심 JoinPoint를 결정하므로, Advice가 실행되는 시기를 제어할 수 있다.

JoinPoint
-. 애플리케이션 실행 흐름에서의 특정 포인트를 의미한다 (클래스 초기화, 메소드 호출 etc..)
-. Spring은 프록시 방식을 사용하여 JoinPoint는 항상 메소드 실행 지점으로 제한된다.

Advice
-. JointPoint에서 수행되는 코드

위 내용만 종합해보면
Advice라는 코드로 적용해야할 대상인 JoinPoint들에서
우리가 Pointcut으로 적용할 놈들만 지정해준다. 라고 이해를 했다.

즉, 내가 생각하기엔
Advice : 공통 적용 코드
JoinPoint : 프로그램 내 모든 메소드
Pointcut : 모든 메소드에서 내가 적용하고 싶은 것만 선택
이라고 생각이 든달까나..?

그 외에 다른 용어 들도 있다.

Weaving
-. Pointcut으로 결정한 타겟의 JoinPoint에 Advice를 적용하는 것을 뜻함
-. 즉, 공통 적용 코드를 비지니스 로직에 적용한다는 의미
-. 간단하게 프록시 방식을 통해서 AOP가 구현되는 과정

AOP proxy
-. AOP 기능을 구현하기 위해 만든 가짜 객체이다.
-. Spring에서 AOP proxy는 JDK동적 프록시, CGLIB 프록시 이다.

Advice 종류

@Before, @AfterReturning, @Afterthrowing, @After, @Around …
위에 애노테이션정도가 종류에 포함되어있곡
가장 강력한 Advice는 @Around이다. 이것하나만 있어도 위에 모든 기능을 제공한다.
하지만 모든기능을 제공하는 만큼 개발자의 실수 유발이 가능하므로
상황에 맞는 Advice를 사용하자

테스트를 위해 계산기 프로그램을 만들어 사용해보자

<계산기 메인 프로그램, 빈등록해서 관리>

@Service
public class NewCalculate {
    public int add (int x, int y){
        System.out.println("--> Methode 실행 : add");
        return x+y;
    }
    public int sub (int x, int y){
        System.out.println("--> Methode 실행 : sub");
        return x-y;
    }
    public int mul (int x, int y){
        System.out.println("--> Methode 실행 : mul");
        return x*y;
    }
    public double div (int x, int y){
        System.out.println("--> Methode 실행 : div");
        return (double) x/y;
    }
}

<AOP 적용 프로그램, 빈등록해서 관리>

@Aspect
@Component
public class AspectCalculate {
    // 변경될 내용들 @Before, @After ~~
}

<Test 실행 프로그램, Junit5>

@SpringBootTest
public class TestCalcualte {
    private final NewCalculate newCalculate;

    @Autowired
    public TestCalcualte(NewCalculate newCalculate) {
        this.newCalculate = newCalculate;
    }

    @Test
    public void addTest(){
        System.out.println(newCalculate.add(100,50));
    }
    @Test
    public void subTest(){
        System.out.println(newCalculate.sub(100,50));
    }
    @Test
    public void divTest(){
        System.out.println(newCalculate.div(100,50));
    }
    @Test
    public void mulTest(){
        System.out.println(newCalculate.mul(100,50));
    }
}

2개의 클래스는 java 폴더에
1개의 클래스는 test 폴더에 넣어 테스트용 프로그램을 만들었다.
4개의 메소드를 호출할때 AOP적용된 프로그램이
어떻게 동작하는지 보자

@Before
1). 조인 포인트 실행 이전에 실행한다.
2). Advice를 구현한 메서드는 일반적으로 리턴타입이 void이다.
3). 메서드에서 예외를 발생시킬 경우 대상 객체의 메서드가 호출되지 않는다.

@Aspect
@Component
public class AspectCalculate {
    @Before("execution(* com.aop.section2aopcalculate..*(..))")
    public void beforeLog(JoinPoint joinPoint) {
        System.out.println("--> Aspect 실행 : " + joinPoint.getSignature().getName());
    }
}

//출력
--> Aspect 실행 : sub
--> Methode 실행 : sub
50
--> Aspect 실행 : add
--> Methode 실행 : add
150
--> Aspect 실행 : mul
--> Methode 실행 : mul
5000
--> Aspect 실행 : div
--> Methode 실행 : div
2.0

보는 것과 같이 메서드가 실행되기전에
Aspect 실행 문구가 출력되는 것을 볼 수있다.

@After
1). 조인 포인트의 메서드 예외나 정상과는 상관없이 실행한다.
2). 메서드 실행 완료 후 공통 기능 실행을 한다.

@Aspect
@Component
public class AspectCalculate {
    @After("execution(* com.aop.section2aopcalculate..*(..))")
    public void AfterLog(JoinPoint joinPoint) {
        System.out.println("--> Aspect 실행 : " + joinPoint.getSignature().getName());
    }
}

//출력
--> Methode 실행 : sub
--> Aspect 실행 : sub
50
--> Methode 실행 : add
--> Aspect 실행 : add
150
--> Methode 실행 : mul
--> Aspect 실행 : mul
5000
--> Methode 실행 : div
--> Aspect 실행 : div
2.0

각 메서드들이 실행 완료된 후에
Aspect 실행 문구 출력되는 것을 볼 수 있다.

@AfterReturning
1). 조인 포인트 실행 완료 후 실행한다.
2). 메서드가 예외 없이 실행된 이후 공통 기능을 실행

@Aspect
@Component
public class AspectCalculate {
    @AfterReturning(value = "execution(* com.aop.section2aopcalculate..*(..))", returning = "value")
    public void AfterReturningLog(JoinPoint joinPoint, Object value) {
        System.out.println("--> Aspect 실행 : " + joinPoint.getSignature().getName());
        System.out.println("--> returning = " + value);
    }
}

//출력
--> Methode 실행 : sub
--> Aspect 실행 : sub
--> returning = 50
50
--> Methode 실행 : add
--> Aspect 실행 : add
--> returning = 150
150
--> Methode 실행 : mul
--> Aspect 실행 : mul
--> returning = 5000
5000
--> Methode 실행 : div
--> Aspect 실행 : div
--> returning = 2.0
2.0

After와 다른점은 returning로 값을 활용할 수가 있다.
위에 예제에서 프린트문을 하나더 추가했고
value라는 변수에 담아 출력하였다.
메서드의 반환값을 출력한 모습을 볼 수 있다.

@AfterThrowing
1). 메서드가 예외를 던지는 경우 실행한다.
2). 메서드를 실행하는 도중 예외가 발생한 경우 공통 기능을 실행한다.
이경우에는 강제로 예외처리를 만들어야한다.

간단하게 add함수에다 만들어서 진행해보자

public int add (int x, int y) throws Exception {
    System.out.println("--> Methode 실행 : add");
    if(x+y>101) throw new Exception("예외");
    return x+y;
}

기존 코드에서 예외처리만 추가했다 x+y값이 101보다 크면
“예외”를 던져주게 프로그램을 작성했다.
예외가 추가됨에따라 기존 Test 코드에도 임시로 throws Exception를
달아주면 정상적으로 테스트코드가 동작할 것이다.

@Test
public void addTest() throws Exception {
    System.out.println(newCalculate.add(100,50));
}

이제 준비는 되었으니 예외 발생시 받을 변수 ex를 설정하고 출력구문을 넣어
AfterThrowing를 적용하여 작동시켜보자.

@Aspect
@Component
public class AspectCalculate {
    @AfterThrowing(value = "execution(* com.aop.section2aopcalculate..*(..))", throwing = "ex")
    public void AfterThrowingLog(JoinPoint joinPoint, Exception ex) {
        System.out.println("--> Aspect 실행 : " + joinPoint.getSignature().getName());
        System.out.println("--> Exception = " + ex.getMessage());
    }
}

//출력
--> Methode 실행 : sub
50
--> Methode 실행 : add
--> Aspect 실행 : add
--> Exception = 예외
--> Methode 실행 : mul
5000
--> Methode 실행 : div
2.0

위의 출력을 보면 알 수 있듯이
sub,mul,div 메서드들은 공통코드인 Aspect코드들이
적용되어 출력되지 않은 모습이다.

오직 add 메서드에서만 예외라는 출력구문이 출력되었다.
즉 예외가 발생한 메서드의 값을 사용할 수 있다는 뜨읏~
그리고 메서드 실행 완료후 Aspect코드가 실행되었다는 점!

@Around
1). 위에 얘기했듯이 이 문법하나로 위에 있는 것들 전부 동작이 가능하다.
호출 전후에 수행이 가능하며 조인 포인트 실행여부 선택, 반환 값 변환, 예외 변환 등이 가능하다.

2). 메서드 실행 전,후, 예외 발생 시점에 공통 기능을 실행한다.
조인 포인트 실행 여부 - joinPoint.proceed()
전달 값 변환 - joinPoint.proceed(args[])
try ~ catch ~ finally 구문처리 가능
다른 얘들과는 다르게 ProceedingJoinPoint 타입을 사용해야한다.

@Aspect
@Component
public class AspectCalculate {
    @Around("execution(* com.aop.section2aopcalculate..*(..))")
    public Object AroundLog(ProceedingJoinPoint joinPoint) throws Throwable{
        System.out.println("--> Aspect 실행 : " + joinPoint.getSignature().getName());
        try {
            return joinPoint.proceed();
        }finally {
            System.out.println("--> Aspect 종료 : " + joinPoint.getSignature().getName());
        }
    }
}

//출력
--> Aspect 실행 : sub
--> Methode 실행 : sub
--> Aspect 종료 : sub
50
--> Aspect 실행 : add
--> Methode 실행 : add
--> Aspect 종료 : add
150
--> Aspect 실행 : mul
--> Methode 실행 : mul
--> Aspect 종료 : mul
5000
--> Aspect 실행 : div
--> Methode 실행 : div
--> Aspect 종료 : div
2.0

위에 예제에서 볼 수 있듯이
joinPoint.procced(); 위,아래로 출력문을 넣어
실행과 종료시점을 적어 두었다.
그렇게 적어놓고 출력해본 결과…

위와 같이 출력된 걸 볼 수 있다.

즉, joinPoint.procced();의 흐름을 제어함에 따라
우리가 원하는 출력시점을 정할 수 있다는점?
이 예제는 하나의 메서드의 실행 시간을 측정하는 프로그램 같은
예제에서 조금 더 어울리는 것 같다는 생각이 든다.


오늘 AOP 공부는 여기까지!!
정말 간단한 예제로 내가 직접 만들어서
테스트를 해보면서 이래저래 알아보는 시간을 가졌다.
내일은 다른 예제로 AOP사용법을 좀 더 익숙하게 적용을 해보자


오늘의 커피량: ☕️ ☕️ ☕️
오늘의 점심: 제육볶음, 밥