jhhan의 블로그

Spring - AOP 본문

Spring

Spring - AOP

jhhan000 2020. 10. 22. 23:20

이번에는 AOP에 대해서 다뤄보겠습니다.

 

전에도 AOP에 대해서 다룬 적이 있는데,,,

그 때는 AOP의 뜻과 개념만 얼핏 서술했습니다.

사실 그 때 AOP에 대해서 알지 못해서 그냥 어떻게든 적고 끝났습니다.

이번에는 코드를 통해서 좀 더 알아보겠습니다.

그래도 AOP를 완전히 이해한 것은 아니어서 설명이 부족할 수도 있겠네요...

 

먼저 AOP가 필요한 상황에 대해서 알아보겠습니다.

○ 예시: 모든 메소드의  호출 시간을 측정할려면??

저런 예시가 있다 할 때 모든 메소드에 측정 로직을 추가할 수도 있겠지만,,,

제가 만드는 작은 프로젝트도 메소드가 10개가 넘어가던데... 10개에 일일이 다 추가하기에는 너무 비효율적입니다..

한번 예시를 들어보죠

 

이전에 작성했던 MemberService 클래스로 예시를 들어보겠습니다.

@Transactional
public class MemberService {
    private final MemberRepository memberRepository;

    @Autowired
    public MemberService(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }

    /**
     * 회원 가입
     */
    public Long join(Member member) {
        /* 중복 회원 안됨 */
        long start = System.currentTimeMillis();

        try {
            validateDuplicateMember(member); // 중복 회원 검증
            memberRepository.save(member);
            return member.getId();
        } finally {
            long finish = System.currentTimeMillis();
            System.out.println("join = " + (finish - start) + "ms");
        }
    }

    private void validateDuplicateMember(Member member) {
        memberRepository.findByName(member.getName())
                .ifPresent(m -> {
                    throw new IllegalStateException("이미 존재하는 회원입니다.");
                });
    }

    /**
     * 전체 회원 조회
     */
    public List<Member> findMembers() {
        return memberRepository.findAll();
    }

    public Optional<Member> findOne(Long memberID) {
        return memberRepository.findById(memberID);
    }
}

join 메서드를 한번 보면...

start와 finish로 시작시간, 끝시간을 구한 후 출력하게 했습니다.

머... 1개 정도야 쉽게 할 수 있겠지만,,, 10개만 넘어가도 귀찮습니다.
(그리고 실제 업무에서는 메서드가 몇십 단위가 아닌 몇백 단위부터 시작할테니... 일일이 다 쓸 생각이 안들겠죠?)

아  저 코드가 잘 돌아가는지 테스트를 해보겠습니다.
(이전에 적어뒀던 MemberServiceIntegrationTest로 실행을 해보죠)

네 join = 195ms로 잘 뜨는 것을 확인할 수 있네요...

 

어쨌든 이래서 AOP라는 개념이 필요합니다.

AOP: Aspect Oriented Programming(관점 지향 프로그래밍.. 이라고 번역됩니다)

AOP는 공통관심사항(cross-cutting concern)과 핵심관심사항(cor concern)을 분리합니다.

약간 용어를 헷갈려하실 수도 있을텐데, 좀 쉽게 말해보겠습니다.(그냥 저만의 방식으로...)

  • 핵심관심사항: 로직의 핵심입니다. 여기서는 회원가입 & 회원조회 같은 것들이 해당됩니다.
  • 공통관심사항: 모든 로직에서 쓸 수 있어야 합니다. 혹은 메서드 안에서 핵심 로직을 제외한
                      다른 부가적인 기능을 가리킨다고 봐도 될 것 같습니다.
                       여기서는 '메서드의 호출시간'이겠네요.

그럼 공통관심사항을 따로 만들어보겠습니다.

현 프로젝트의 구조입니다. (-> 와우 어느덧 추가된게 되게 많네요..)

aop라는 패키지를 만들고, TimeTraceAop라는 클래스를 만듭니다.

@Aspect
@Component
public class TimeTraceAop {

    @Around("execution(* hello.hellospring..*(..))")
    public Object execute(ProceedingJoinPoint joinPoint) throws Throwable {
        long start = System.currentTimeMillis();
        System.out.println("START: " + joinPoint.toString());
        try {
            return joinPoint.proceed();
        } finally {
            long finish = System.currentTimeMillis();
            long timeMs = finish - start;
            System.out.println("END: " + joinPoint.toString() + " " + timeMs + "ms");
        }
    }
}

코드는 위와같이 적습니다.

  • @Aspect: 이 클래스가 aspect라는 것을 나타냅니다.
  • @Component: 스프링 빈으로 등록합니다. (혹은 예전에 작성했던 SpringConfig에 추가하셔도 됩니다.)
  • @Around: 특정 타겟 메서드를 감싸서 실행한다는 표시이다.
       추가 : @Before: 메소드가 실행되기 전에 
               @After: 메소드가 실행된 후에
       -> 어느 시점에 적용할 지를 정한다.
  • execution: 해당 패키지 경로에 포함되는 녀석을 실행한다.
       여기서는 hello.hellospring.. 이라고 되어있는데, 현재 저의 패키지 구조가 hello.hellospring..으로 되어있다.
       즉, 모든 메서드에 적용한다는 뜻임.
  • execution의 경우 본인이 원하는 것에서만 실행하게 할 수 있다.
       예를 들어 "execution(* hello.hellospring.service..*(..))" 라고 쓰면 hello.hellospring.service 밑에 있는
       메서드에서만 진행이 된다.
  • ProceedingJoinPoint: 아직 이 부분을 조금 모릅니다. 어쨌든 joinPoint.proceed()라는 메서드를 사용하면
        다음 메소드로 진행을 할 수 있게 해준다고 합니다.. 좀 어렵네요..
  • System.currentTimeMillis() : 현재 시간을 밀리세컨드 단위로 받게 한다.

코드에 대한 간략 설명은 이정도면 될 것 같네요..

아, 혹시 @Component가 싫고 SpringConfig로 진행하고 싶은 경우 아래와 같이 합니다.
(일단 @Component를 지워줍니다.)

@Configuration
public class SpringConfig {

    private final MemberRepository memberRepository;

    @Autowired
    public SpringConfig(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }

    @Bean
    public MemberService memberService() {
        return new MemberService(memberRepository);
    }

    @Bean
    public TimeTraceAop timeTraceAop() {
        return new TimeTraceAop();
    }
}

SpringConfig 파일에 다음과 같이 적으면 스프링 빈 등록이 됩니다.

그럼 이제 실행시켜 보죠.

실행하면 바로 이런 로그가 남는 것을 확인할 수 있습니다.

그러면 http://localhost:8080/members 주소로 들어가 봅니다. 그리고 로그 창을 보면...!!

이런 로그가 남는 것을 확인할 수 있습니다.

각각 MemberController, MemberService, JpaRepository를 사용했네요.

 

그럼 아까 execution을 바꿔보겠습니다.

execution(* hello.hellospring.service..*(..)) 으로 바꾸고 Rerun을 해보면...

아까보다는 로그가 줄어든 것을 확인할 수 있습니다.

MemberService만 사용했네요. service 패키지 밑에는 MemberService밖에 없으니 당연한 결과같습니다.

 

 

이렇게 간단 AOP를 진행해봤습니다.

  1. 회원가입 & 회원 조회 같은 핵심관심사항과 시간 측정인 공통관심사항이 분리된 것을 알 수 있습니다.
  2. 그리고 시간을 측정하는 로직을 별도의 공통 로직으로 만들었습니다.
  3. 이렇게 하면 핵심사항과 공통사항을 따로 그리고 깔끔하게 유지할 수 있습니다.
  4. 변경이 필요한 경우 해당 로직만 변경하면 바로 반영이 됩니다.
  5. 그리고 적용대상 역시 선택할 수 있습니다.

AOP를 적용하니 코드 관리가 훨씬 깔끔하네요.. ㅎㅎ

 

 

이렇게 AOP에 대해서 조금 더 알아봤습니다.

어차피 이건 기초적인 거니깐 ,,, 이걸 기반으로 AOP에 대해서 더 자세히 알았으면 하네요..

사실 AOP를 이렇게 써보긴 했지만, 제가 직접 AOP를 적용하기에는 아직은 어려울 것 같습니다.

AOP는 계속 봐도 어렵네요...

 

 

이렇게 AOP 글을 마무리하겠습니다.

'Spring' 카테고리의 다른 글

Spring - Excel 파일 다운로드(간단)  (4) 2021.01.06
스프링 코어(1) - 스프링 컨테이너 & 빈  (1) 2021.01.04
Spring - JdbcTemplate  (0) 2020.10.17
Spring - 웹 기능(MVC)  (0) 2020.09.28
Spring - DI(2)  (0) 2020.09.26