AOP

구름코딩·2020년 10월 19일
0
  • 모든 메소드의 호출 시간을 측정하고 싶다면?
@Transactional
public class MemberService {

    private final MemberRepository memberRepository;

    //Dependency Injection
    //현 클래스에서 생성하는 것이 아닌 외부에서 생성한 것을 넣어주는 형태
    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();
            long timeMs = finish - start;
            System.out.println("join =" + timeMs+"ms");
        }
    }

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

    /**
     * 전체 회원 조회
     */
    public List<Member> findMembers() {
        long start = System.currentTimeMillis();
        try {
            return memberRepository.findAll();
        } finally {
            long finish = System.currentTimeMillis();
            long timeMs = finish - start;
            System.out.println("findMembers ="+timeMs+"ms");
        }
    }

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

join과 findMembers 메소드에 시간측정을 위한 코드를 추가하였다. 만약 이 서비스클래스에 메소드가 수십, 수백개 였다면 유지관리 및 보수가 어떨까?
-> 매우 힘듬

문제 사항

  • 회원가입, 회원 조회에 시간을 측정하는 기능은 핵심 관심 사항이 아니다 (단순히 시간 측정용)
  • 시간을 측정하는 로직은 공통 관심 사항이다 (모든 메소드의 시간을 측정)
  • 시간을 측정하는 로직과 핵심 비지니스의 로직이 섞여서 유지 보수가 힘들다
  • 시간을 측정하는 로직을 별도의 공통 로직으로 빼서 만들기가 어렵다
  • 시간을 측정하는 로직을 변경하려면 각 로직마다 일일히 변경을 해야한다

이를 위해 AOP 적용

  • Aspect Oriented Programming 관점 지향 프로그래밍
Before

After

문제를 공통 관심 사항 vs 핵심 관심 사항으로 분리한다

Cross-cutting-concern VS Core-concern

적용

AOP를 위한 TimeTraceAop 클래스를 만들어준다

  • @Aspect를 달아줘야 한다 (관점지향)
  • @Component 를 달아줘서 Bean으로 등록해준다 그래야 스프링에서 관리가 가능
  • @Around 를 통해서 원하는 타켓을 정할수 있다
    • "execution(* <원하는 폴더의 파일>(..))"
    • "execution( hello.hellospring..(..))")
    • hello.hellospring 하위 모든 파일에 적용하겠다는 의미이다
  • joinPoint에는 다양한 메소드가 존재하므로 원하는 메소드를 적절히 선택하여 다양한 활용이 가능하다
    • 예를 들어 특정 조건에서는 다음 로직이 진행되지 않게 하는 것도 가능
@Aspect
@Component
public class TimeTraceAop {


    @Around("execution(* hello.hellospring..*(..))") //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");
        }
    }

}

결과

  • 회원가입, 회원 조회등 핵심 관심사항과 시간을 측정하는 공통 관심 사항을 분리한다
  • 시간을 측정하는 로직을 별도의 공통 로직으로 만든다
  • 핵심 관심 사항을 깔끔하게 유지할 수 있다 (코드를 덕지덕지 붙이지 않아도 된다)
  • 변경이 필요하면 이 로직만 변경하면 된다
  • 원하는 적용 대상을 선택할 수 있다 (@Around)

스프링의 AOP 동작 방식

기존 스프링 의존 관계에서 클래스 호출 방식

helloController ----> memberService 의존하고있다
helloController에서 memberService의 메소드를 호출할때 memberService에서 호출

AOP 적용 후 의존 관계에서 클래스 호출 방식

AOP가 있다면 스프링이 호출받는 memberService의 가짜 memberService(프록시 Proxy)를 만들어 낸다. 그럼 프록시(가짜)가 불리고 끝난 이후 joinPoint.preceed()가 불리고 나서 실제 memberService를 호출한다

profile
내꿈은 숲속의잠자는공주

0개의 댓글