스프링 공부/인프런 김영한 스프링 입문 노트정리

7-2. AOP 적용

모항 2022. 8. 19. 22:21

AOP가 무엇인지 알아보고, AOP를 적용하는 모습을 코드로 직접 확인하는 회차이다.

 

 

 

 

 

AOP(Aspect Oriented Programming)란?

AOP를 간단하게 설명하자면 다음과 같다.

 

프로그램의 각 기능을 실행하는 데 시간이 얼마나 걸리는지 측정해야 하는 상황에서,

AOP 식으로 개발하지 않으면 아래 그림처럼 해야 한다.

 

 

하지만 AOP를 한다면 다음과 같은 상황이 된다.

 

 

객체지향적 프로그래밍과 DI를 통하여 각 구성요소를 알맞은 단위로 잘 나누어놓았기 때문에 가능한 일이다.

AOP 클래스 속에 코드를 적어주고, 그것을 어디에 적용할지 지정하면 된다.

 

 

 

 

 

 

 

AOP 코드 짜보기

AOP 코드를 위한 패키지를 만들어주고 그 안에 클래스를 만든다.

실행시간 측정용이므로 TimeTraceAop라고 이름을 지었다.

 

 

머리에는 이 어노테이션을 붙여줘야 한다.

 

 

그리고 위와 같이 코드를 채워준다.

 

 

설명하자면 다음과 같다.

 

 

1. Throwable

Throwable은 자바의 모든 예외와 오류들의 상위 클래스이다.

 

 

2. 코드 진행

  1. 먼저, 시작하는 시점에서의 현재 시각을 밀리초 단위로 long start에 저장한다.
  2. 어떤 작업을 시작하는지 System.out.println()으로 콘솔에 띄워준다. joinPoint.toString()을 이용하면 작업의 이름을 편리하게 얻어올 수 있다.
  3. try문 내에서 해당 작업을 수행한다. joinPoint.proceed() 요놈이 바로 작업을 수행하라는 명령문이다. proceed()의 결과물은 리턴을 해준다.
  4. finally문에서 종료시각을 알아온다. 종료시각에서 시작시각을 빼서 소요시간을 알아낸다. 종료 메시지와 소요시간을화면에 출력한다.

 

 

try와 finally에 대한 흥미로운 점 한 가지(접은글)

더보기

try문을 살펴보면 return이 있다.

그럼 try문에서 예외나 오류가 발생하지 않으면 return 때문에 즉시 이 함수가 종료되고 finally는 실행이 안 되는 것일까?

 

그렇지 않다!

 

finally의 호출 시점이 try와 catch 이후이기는 하다. 하지만 try와 catch에 return이 들어있어도 반드시 finally는 호출된다.

 

프로그램을 완전히 죽여버린다던지(System.exit()) try/catch문에서 무한 루프가 돈다던지 하는 상황이 아니라면 finally는 반드시 실행된다.

 

아래는 이와 관련된 스택오버플로우 질문글이다.

https://stackoverflow.com/questions/65035/does-a-finally-block-always-get-executed-in-java

 

Does a finally block always get executed in Java?

Considering this code, can I be absolutely sure that the finally block always executes, no matter what something() is? try { something(); return success; } catch (Exception e) { ...

stackoverflow.com

 

 

 

 

 

 

 

스프링 빈 등록

 

이제 스프링 빈 등록을 해준다.

 

이렇게 @Component 어노테이션을 적어서 등록해줘도 되는데

 

 

어떤 빈들이 등록되었는지를 SpringConfig 문서에서 쫙 한 번에 보는 게 편하니까 어노테이션을 적는 대신 SpringConfig에서 등록하겠다.

강사님의 말씀에 따르면

Domain, Repository, Service와 같은 것들은 정형화되어있다고 말해도 될 정도로 하도 많이 쓰니까 @Component로 등록해도 잊거나 헷갈리는 상황이 잘 없다고 한다.

그러나 이런 특수한 요소의 경우에는 한 눈에 보이게 Configuration 문서에 등록해주는 게 낫다고 한다.

 

 

SpringConfig에 다음과 같이 직접 빈으로 등록한다.

 

 

 

 

 

 

 

 

@Around 설정해주기

이제 이 AOP가 어떤 요소들에 적용될 것인지 지정해준다.

 

 

이렇게 execute 메소드에 @Around 어노테이션을 붙여주면 된다.

AOP가 어떤 요소에 적용될지 결정하는 것은 @Around 어노테이션의 내용물로 들어가는 문자열이다. 이것을 쓰는 문법이 따로 마련되어있으니 그것을 참고해서 만들어 적으면 된다.

아주 다양하게 적을 수 있도록 되어있는데, 그 중에 실무에서 많이 쓰는 건 5%도 안 된다고 하니 겁먹지 말고 필요할 때 잘 찾아 사용해보자.

 

우리가 지금 적은 문자열은 hello.hellospring 패키지 안에 있는 모든 것에 적용한다는 의미이다.

 

 

 

 

 

 

 

돌려보기 ( + 순환 참조 문제 해결)

이제 돌려보면 되는데...

안 된다...!

메시지를 보니 순환 참조 때문이다.

 

 

테스트도 똑같은 이유로 실패한다.

 

 

그런데 Aop만 빈에서 내려주면 정상적으로 된다.

프로그램 잘 돌아가고

 

 

웹사이트도 잘 뜨고

 

 

테스트도 다 잘 된다. 아니 이게 무슨 일이야

 

 

메시지에 표시된 멤버컨트롤러와 멤버서비스는 건드리지도 않았는데 어떻게 AOP 하나 없앴다고 이렇게 갑자기 잘 돌아갈 수가 있는겨

 

아무래도 Aop가 문제인 듯 한데, 아무리 비교해봐도 강사님의 코드와 내 코드는 똑같다... 어째서 안 되지...? 저번 데이터베이스 접속 때처럼 Spring이 업데이트되어 새로 생긴 문제인가?

 

이 다음 노트정리는 일단 이 문제부터 해결하고 이어서 하겠다...

 

라고 말한 지 3분만에 해결되었다.

아래는 도움을 받은 질문글이다.

 

AOP(TimeTraceAop)를 @Component 로 선언 vs SpringConfig에 @Bean으로 등록 - 인프런 | 질문 & 답변

안녕하세요. 김영한 팀장님,  AOP(TimeTraceAop)를 @Component로 선언하지 않고 SpringConfig에 @Bean으로 등록할 수 있다고 설명하셨는데 실제로 코드를 돌려보면 빈 순환 참조 에러가 발생합니다. 강의대로

www.inflearn.com

역시

내가 질문글을 올리고 싶을 때에는

같은 문제로 올라온 질문글이 반드시 이미 존재한다.

심지어 5일 전에도 똑같은 문제로 질문을 한 사람이 있다. 사람 사는 거 다 똑같다.

 

 

SpringConfig에서 내리고 @Component 어노테이션으로 빈을 등록하니

문제 없이 테스트가 pass될 뿐만 아니라

AOP도 잘 작동한다. 콘솔 창에 START: 와 END: 출력문이 잘 보이는 것이 확인된다.

 

 

실제 실행을 해보아도 잘 된다.

 

 

사이트의 기능(전체 회원 목록 보기)을 실행시켜보면

 

 

AOP가 실행시간 표시도 잘 해준다.

 

 

 

그래서 이런 문제가 왜 발생하느냐?

왜 @Component로 빈을 등록하면 문제가 없는데 SpringConfig에서 등록하면 순환 참조가 발생하느냐?

 

 

TimeTraceAop을 만드는 생성자가 TimeTraceAop 자기 자신을 생성하고 있기 때문이다.

엥? 그건 다른 생성자들도 마찬가지 아닌가요?

그건 맞지만, 다른 생성자들은 문제가 안 된다. 다른 생성자들은 AOP가 아니지만 이놈은 AOP라는 게 중점이다.

 

AOP가 작동할 때, SpringConfig에 있는 이 AOP 생성자도 AOP의 대상에 들어간다. 우리가 hello.hellospring에 포함된 모든 코드를 대상으로 지정했기 때문이다.

그럼 AOP는 자기 자신을 생성하는 코드를 자기 자신이 실행해야하는 상황에 처한다.

그래서 순환 참조가 발생한다.

 

 

이러한 문제를 막기 위해서는

이렇게

AOP의 대상에서 SpringConfig를 제외해주기만 하면 된다!

 

 

이제 @Component를 지워주고, SpringConfig의 AOP 빈 등록 코드도 다시 잘 적어준 뒤에 실행시켜보자.

이야 잘 된다

 

 

테스트도 잘 된다!

 

 

 

 

 

 

 

AOP의 작동방식: Proxy

AOP를 작동시키면 Proxy라는 것이 등장한다.

 

AOP가 없었을 때는

프로그램을 실행하면, 실제 MemberService 객체와 실제 SpringDataJpaMemberRepository 객체... 등등 실제 객체들이 바로 튀어나와 일을 하였다.

 

그런데 AOP가 있으면

그렇게 바로 본체가 나오는 게 아니고, 프록시라는 가짜 객체가 먼저 나선다고 한다.

그 다음에 어떠어떠한 과정을 거쳐 joinPoint.proceed()를 만나면! 그때서야 본 객체가 실행된다.

 

프록시와 그 작동 과정에 대한 설명은 지금 하기엔 너무 방대해서, 다음에 심화 강의에서 해주시겠다고 한다.

이번에는 프록시라는 것이 존재한다는 것만 알고 넘어가자.

 

이렇게 오늘 배우고 사용해본 것처럼 프록시를 사용하는 방식의 AOP도 있고,

이전 회차에서 예로 들었던, 핵심 요소들의 실제 코드 사이사이에 진짜로 특정 코드를 집어넣는 일을 알아서 해주는 방식의 AOP도 있다고 한다.