본문 바로가기
Spring_inflearn/입문

[Spring] inflearn 스프링 입문 - AOP

by clolee 2022. 9. 16.

AOP가 필요한 상황

1. 모든 메소드의 호출 시간을 측정하고 싶을 때

 

기존 방법 - 메소드마다 시작 끝 시간 측정

src/main/java/hello/hellospring/service/MemberService.java

	public Long join(Member member) {
		long start = System.currentTimeMillis();
		try {
			// 같은 이름이 있는 중복 회원x
			validateDuplicateMember(member); // 중복 회원 검증

			memberRepository.save(member);
			return member.getId();
		} finally {
			long finish = System.currentTimeMillis();
			long timeMs = finish - start;
			System.out.println("join = " + timeMs + "ms");
		}
	}

 

통합테스트 실행해보기

 

localhost:8080 에서 회원가입과 회원 목록 실행해보기

처음 동작 시 더 오래 걸림 (클래스 메타 데이터 로딩 등등...)

실무 활용 시 성능 많이 받는 서버는 올리고 웜업. 이것저것 호출해둠

 

시간 측정 - 핵심기능 아님.

시간 측정 로직은 공통 로직. 여러 메소드에 시간 측정하는 공통 기능. 공통 관심 사항. (핵심 비지니스 로직은 핵심 관심 사항)

시간 측정 로직과 핵심 비지니스 로직이 섞이면 유지보수에 어려움

 

2. 공통 관심 사항(cross-cutting concern) 과 핵심 관심 사항(core concern)이 구분될 때

 

 

AOP 적용

이런 문제를 해결하는 기술 : AOP (Aspect Oriented Programming , 관점 지향 프로그래밍)

공통 관심 사항(cross-cutting concern) 과 핵심 관심 사항(core concern) 분리

공통 관심 사항 (ex 시간 측정 로직)을 한 군데 모으고 내가 원하는 곳에 적용.

 

AOP로 쓰려면 @Aspect 어노테이션 필요.

 

TimeTraceAop를 스프링 빈으로 등록

@Component 어노테이션 추가 or 스프링 빈에 등록해서 사용

 

@Around()

공통 관심 사항을 어디에 적용할 것인지 타겟팅

패키지 명.. 하위 클래스명(파라미터 타입)

@Around("execution(* hello.hellospring..*(..))") // 패키지 하위는 다 적용

 

호출될 때마다 joinPoint에다가 여러 파라미터를 통해 조작.

메소드 호출할 때마다 중간에서 인터셉트 걸림. 

필요한 경우 다음으로 호출 안 할 수 있음. 조건 걸기.

중간에 인터셉팅해서 풀 수 있는 기술이 AOP

 

src/main/java/hello/hellospring/aop/TimeTraceAop.java

package hello.hellospring.aop;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

@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");
        }
    }
}

 

 

서버 실행

회원 목록 조회

어디서 병목이 있는지 바로 찾을 수 있음

 

AOP를 통해

회원가입, 회원 목록 조회와 같은 핵심 관심 사항과 시간 측정의 공통 관심 사항을 분리

시간 측정 로직을 공통 로직으로 만듦

핵심 관심사항은 변경 없이 유지 가능

변경 시 분리된 공통 관심사항의 시간 측정 로직만 변경하면 됨

원하는 적용대상 선택 가능

 

service와 service 하위만 시간 측정 로직 적용해보기

src/main/java/hello/hellospring/aop/TimeTraceAop.java 에서

@Around("execution(* hello.hellospring.service..*(..))") 로 변경

 

회원 목록 조회

service에서만 execution 찍힘

 

AOP가 동작하는 방식 (여러 가지 방법이 있음)

스프링의 AOP 동작 방식

 

AOP 적용 전

memberController가 memberService를 의존하고 있고 memberController에서 메소드 호출하면 memberService의 메소드 호출

 

AOP 적용, 적용 대상 지정 (memberService 지정)

스프링은 가짜 memberService (프록시)를 만듦.

스프링 컨테이너에 스프링 빈을 등록할 때 가짜 스프링 빈 memberService를 앞에 세움

가짜 스프링 빈이 끝나고 joinPoint.proceed()하면 그때 진짜 memberService를 호출.

memberController가 호출하는 것은 진짜 memberService가 아닌 프록시 기술로 발생하는 가짜 memberService

 

확인 방법

src/main/java/hello/hellospring/controller/MemberController.java

memberController에서 memberService가 injection 됨

@Controller
public class MemberController {

    private MemberService memberService;

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

getClass() 했을 때 service.MemberService에서 끝나지 않음

CGLIB : CG Library

memberService를 가지고 복제를 해서 코드를 조작하는 기술

 

스프링 컨테이너가 AOP가 적용되면 가짜 프록시를 앞에 세움. 프록시를 통해 AOP가 실행되고 

 joinPoint.proceed()하면 그때 진짜 클래스가 호출됨

 

스프링의 AOP : Proxy 방식의 AOP

댓글