본문 바로가기
  • 개발공부 및 일상적인 내용을 작성하는 블로그 입니다.
Spring basic

스프링 입문 : 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술 - AOP #2

by 방구석 대학생 2021. 11. 17.

"인프런의 스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술 강의를 듣고 작성한 글 입니다."

https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%EC%9E%85%EB%AC%B8-%EC%8A%A4%ED%94%84%EB%A7%81%EB%B6%80%ED%8A%B8

 

[무료] 스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술 - 인프런 | 강의

스프링 입문자가 예제를 만들어가면서 스프링 웹 애플리케이션 개발 전반을 빠르게 학습할 수 있습니다., 스프링 학습 첫 길잡이! 개발 공부의 길을 잃지 않도록 도와드립니다. 📣 확인해주세

www.inflearn.com

 

이전 강의 정리글에서 AOP가 적용되어 각 메소드들의 시간 측정로직이 잘 동작하는 것을 확인해 볼 수 있었다.

START : execution(String hello.hellospring.controller.HomeController.home())
END : execution(String hello.hellospring.controller.HomeController.home()) 5ms
START : execution(String hello.hellospring.controller.MemberController.list(Model))
START : execution(List hello.hellospring.service.MemberService.findMembers())
START : execution(List org.springframework.data.jpa.repository.JpaRepository.findAll())
Hibernate: select member0_.id as id1_0_, member0_.name as name2_0_ from member member0_
END : execution(List org.springframework.data.jpa.repository.JpaRepository.findAll()) 124ms
END : execution(List hello.hellospring.service.MemberService.findMembers()) 130ms
END : execution(String hello.hellospring.controller.MemberController.list(Model)) 144ms

 

이런식으로 시간 측정로직을 적용해주면 각 기능별로 어디서 시간이 오래 걸리고, 어디서 병목 현상이 발생하는지 점검해 볼 수 있게된다.

 

AOP를 잘 활용하면 ProceedingJoinPoint 객체에서 제공해주는 각종 메소드들을 통해 얻을 수 있는 것들에 한해서 원하는 대로 조작을 할 수도 있을뿐 더러,

AOP 는 각종 기능 메소드들이 호출될 때마다 인터셉트로 걸려서 기능이 수행되는데 이를 이용햇 조건을 걸어서 proceed 메소드가 동작하지 못하게 만들어 줄 수도 있다.

(중간에 인터셉트로 걸려서 실행되는 특징 때문에 메소드 시작지점에 필요한 동작들이 AOP 를 통해 수행되고 나면 proceed 메소드를 통해 반드시 본래 실행 시키려 했던 메소드들이 실행 될 수 있게끔 흐름을 진행 시켜주어야 한다.)

 

AOP 를 사용하면 다음과 같이 문제점들을 해결해 줄 수 있다.

1. 회원가입, 회원조회 등 핵심 관심사항과 시간을 측정하는 공통 관심사항을 분리해 줄 수 있다.

2. 시간을 측정하는 로직을 별도의 공통 로직으로 만들 수 있다.

3. 핵심 관심사항을 깔끔하게 유지할 수 있다.

4. 변경이 필요하면 AOP 에 작성된 로직만 변경해주면 된다.

5. 원하는 적용 대상을 선택할 수 있다.(@Around 어노테이션 에서 적용대상 선택가능, 보통 패키지 레벨로 많이 선택한다.)

 

그럼 AOP 는 구체적으로 어떻게 동작하는걸까?

 

AOP 의 동작 원리

AOP를 적용하기 전 스프링에서의 클래스간 의존관계는 다음과 같다.

 

AOP 를 적용하기 전 스프링에서의 클래스간 의존관계는 지금까지 공부해온 대로 그냥 스프링 빈 등록 이후 생성자를 통한 의존성 주입 등으로 클래스간 의존관계를 형성해주는 것이다.

즉, 클래스들 사이에 끼어들어 오는 것이 없다.

 

그런데 AOP 를 사용하고 @Around 어노테이션을 통해 어디에 AOP를 적용할 건지 지정해주고 나면 의존관계가 완전히 달라진다.

 

만약 MemberService 클래스에 AOP 가 적용되었다고 한다면 의존관계를 가지는 클래스들의 중간에서 가짜 MemberService 클래스를 만들어내는데, 이를 프록시 객체라고 한다.

이렇게 생성된 가짜 MemberService 는 각 클래스들을 스프링 빈으로 등록할 때 실제 MemberService 객체의 앞에 세워진다.

그리고 앞에 등록된 가짜 객체에 대한 수행이 끝나고 proceed 메소드가 동작하면 그때서야 실제 MemberService 객체로 넘어가서 동작을 수행하게 된다.

즉, 의존관계에 있어서 MemberService 를 주입받는 경우 의존성을 주입받게 되는 것은 실제 객체가 아니라 프록시로 만들어진 가짜 객체인 것이다.

 

이를 실제로 확인하는 방법이 있다.

MemberController 에서 MemberService 객체가 의존성 주입될 때 확인해 보는것이 가능하다.

사실 별건 아니고, 의존성을 주입해주는 생성자에서 출력문을 작성해주고, 여기서 MemberService 객체에 대해 getClass() 메소드를 사용해주면 된다.

- MemberController.java

private final MemberService memberService;

@Autowired
public MemberController(MemberService memberService) {
    this.memberService = memberService;
    System.out.println("memberService = " + memberService.getClass());
}

 

위와 같이 출력문을 작성해주고 어플리케이션을 다시 동작시켜 보면 다음과 같은 로그를 출력하는 것을 확인해 볼 수 있다.

memberService = class hello.hellospring.service.MemberService$$EnhancerBySpringCGLIB$$569c0b8e

로그를 자세히 보면 단순히 MemberService 에서 문자열이 끝나는게 아니라, 그 뒤에 Enhancer 어쩌고 저쩌고 하더니 CGLIB 가 나온다.

여기서 CGLIB 는 CG 라이브러리 라는 뜻인데, 여기서 CG 라이브러리란 MemberService 와 같은 객체들을 복제해서 코드를 조작해주는 기술이다.

 

요약하자면 스프링에서 AOP 가 적용되었을 때, 의존성 주입 시 스프링이 AOP 가 적용된 클래스를 확인하면 CG 라이브러리를 통해 해당 클래스의 가짜 객체(프록시 객체) 를 생성해서 실제 객체 앞에 세워두고,

이로 인해 의존성 또한 실제 객체가 아닌 프록시로 만들어진 가짜 객체가 주입되면서 AOP 에 작성해놓은 각종 로직이 각 메소드가 동작하기 전에 흐름을 인터셉트 하여 수행되게끔 만들어 줄 수 있다.

 

스프링에서는 이를 프록시 방식의 AOP 라고 한다.

* 참고 : 좀 더 심화했을 땐 어떤게 있나면, 아예 자바 소스코드의 컴파일 타임 때 코드의 위아래로 AOP 와 같이 특정 로직들을 집어 넣어주는 것도 가능하다고 한다.