Spring_입문07_AOP

4580 단어 SpringSpring

1. AOP가 필요한 상황

🔲 모든 메서드의 호출 시간을 측정하고 싶을 때

MemberService의 join 함수의 호출 시간을 측정해보자.

public Long join(Member member){
    // 같은 이름이 있는 중복 회원 가입 불가.
    long start = System.currentTimeMillis();

    try {
        validateDuplicateMember(member); // 중복 회원 검증
        memberRepository.save(member);
        return member.getId();
    } finally {
        long finish = System.currentTimeMillis();
        long timeMs = finish - start;
        System.out.println("join = "+timeMs+"ms");
    }
}
  1. 메서드가 시작될 때, 시작 시간 start 측정.

  2. 메서드가 끝날 때 시간을 찍어야 하는데, 예외가 발생해도 찍어야 하기 때문에 try-finally 사용. finally는 항상 들어감.

위의 코드에서, 회원가입을 하는 핵심 로직을 "핵심 관리 사항(core concern)"이라 하고, 시간을 측정하는 기능은 여러 메서드에 적용될 수 있는 공통의 로직이기 때문에 "공통 관심 사항(cross-cutting concern)"이라 한다.


메서드가 수천 개 있다고 가정했을 때, 위 코드와 같이 작성하면 아래와 같은 문제들이 있다.

✔ 시간 측정 로직을 모든 메서드에 작성하는 것은 힘들다.

✔ 시간을 측정하는 로직을 별도의 공통 로직으로 만들 수는 있으나 너무 어렵고 복잡하다.

✔ 시간을 측정하는 로직과 핵심 비지니스 로직이 섞여있어 유지보수가 어렵다.

✔ 시간을 측정하는 로직을 변경할 때, 모든 로직을 찾아가면서 변경해야 한다.

이러한 상황에서 AOP를 사용하면 문제를 효과적으로 해결할 수 있다.



2. AOP 적용

2.1 AOP란?

위에서의 문제를 해결하는 기술을 AOP(Aspect Oriented Programming, 관점 지향 프로그래밍)라고 한다. AOP는 공통 관심 사항과 핵심 관심 사항을 분리하는 것이다. 위에서는 메서드에 시간측정 로직을 다 붙였지만, AOP는 시간측정 로직을 한 곳에 다 모으고 내가 원하는 곳에 적용하는 것이다.

✔ AOP 적용 전 시간 측정 로직

✔ AOP 적용 후 시간 측정 로직


2.2 적용

@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");
        }
    }
}
  1. aop라는 패키지를 만든다.

  2. 그 안에 TimeTraceAop라는 클래스를 만든다.

  3. AOP는 @Aspect라는 어노테이션을 붙여줘야 한다.

  4. 시간 측정 로직을 만든다.

  5. AOP를 스프링 빈으로 등록한다.

  6. @Around 어노테이션을 이용해 공통 관심 사항을 적용할 범위를 설정한다.


🔲 joinPoint

AOP는 메서드를 호출할 때마다 중간에서 인터셉트가 걸려 원하는 것을 조작할 수 있다.

joinPoint.toString() : 어떤 메서드를 호출한지 이름을 얻을 수 있다.
joinPoint.proceed() : 다음 메서드로 진행된다.

위의 두 가지 외에도 여러가지가 더 존재한다. 찾아서 사용하자.

🔲 AOP를 스프링 빈에 등록하기

AOP를 스프링 빈으로 등록해야 한다. 이때, SpringConfig에 @Bean을 이용해 등록하거나 @Component 어노테이션을 붙여주어 컴포넌트 스캔을 이용해 등록할 수 있다. SpringConfig에 직접 등록해주는 방식이 더 좋지만 여기서는 컴포넌트 스캔을 사용하겠다.

🔲 @Around를 이용해 공통 관심사항을 적용할 범위 설정하기

@Around 어노테이션을 이용하면 공통 관심사항을 적용할 범위를 설정할 수 있다.

@Around("execution(* 패키지명..클래스명(파라미터 타입))")
@Around("execution( hello.hellospring..(..))") : hello.hellospring 패키지 하위에 다 적용해

🔲 정리

AOP를 통해,

✔ 핵심 관리 사항과 공통 관심 사항 분리.

✔ 핵심 관심 사항을 깔끔하게 유지.

✔ 시간을 측정하는 로직을 별도의 공통 로직으로 만듬. 따라서 변경이 필요하면 이 로직만 변경.

✔ 원하는 적용 대상을 선택할 수 있음.


2.3 AOP의 동작

AOP가 동작하는 방법은 여러가지가 있다. 스프링에서 AOP가 동작하는 방식을 알아보자.

✔ AOP 적용 전 의존관계

AOP를 적용하기 전에는 컨트롤러에서 서비스를 호출할 때는 그냥 의존관계를 통해 호출한다.


✔ AOP 적용 후 의존관계

그런데, 스프링은 AOP가 적용되면 "프록시"라고 하는 가짜 memberService를 만들어 컨테이너에 스프링 빈을 등록할 때 진짜 스프링 빈이 아닌 가짜 스프링 빈을 앞에 세운다. 그래서 가짜 스프링 빈이 끝나고 joinPoint.proceed()하면 그때 진짜 스프링 빈을 호출해준다. 따라서 memberController가 호출하는 것은 진짜 memberService가 아니라 프록시라는 기술로 발생하는 가짜 memberService이다.


✔ AOP 적용 후 전체 그림


스프링 컨테이너에서 스프링 빈을 관리하면 가짜를 만들어서 의존성을 주입해주면 된다. 이것이 의존성 주입의 장점이다. 의존성을 주입하지 않고 직접 컨트롤러에서 서비스를 new하면 이런 기술 자체가 불가능해진다. 컨트롤러 입장에서는 의존성을 주입해주니까 그냥 받아서 사용하는데, 이때 진짜가 아닌 프록시가 들어오는 것이다. 이것 때문에 AOP가 가능한 것이다.

이런것을 스프링에서 프록시 방식의 AOP라고 한다.

좋은 웹페이지 즐겨찾기