Blog

[SpringIntro] AOP 활용

Category
Author
citeFred
citeFred
Tags
PinOnMain
1 more property
AOP를 통한 공통 관심 사항 분리
Table of Content

메서드의 시간을 측정해보자

다음처럼 메서드 실행 시간을 측정하려면 단순히 메서드의 시작부분과 종료부분의 시간을 계산하면 돼지만 프로젝트 규모가 커질수록 중복되는 코드량이 많아진다. 또한 시간 측정 로직이 본래 서비스 코드보다 훨씬 많은양을 차지하면서 가독성이 떨어지게 된다.
MemberService.java 의 시간 측정 코드를 추가했고 정상적으로 작동하지만 좋은 코드는 아니다.
package com.example.demoapp.service; import com.example.demoapp.domain.Member; import com.example.demoapp.repository.MemberRepository; import lombok.AllArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.List; import java.util.Optional; @Service @AllArgsConstructor public class MemberService { private final MemberRepository memberRepository; /** * 회원 가입 */ @Transactional public Long join(Member member) { long start = System.currentTimeMillis(); try { validateDuplicateMemberName(member); memberRepository.save(member); return member.getId(); } finally { long finish = System.currentTimeMillis(); long timeMs = finish - start; System.out.println("join method = " + timeMs + "ms"); } } private void validateDuplicateMemberName(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("findAll method = " + timeMs + "ms"); } } /** * 특정 회원 조회 */ public Optional<Member> findOne(Long memberId) { return memberRepository.findById(memberId); } }
Java
복사

AOP 사용해보기 (Aspect Oriented Programming)

과제처럼 공통 관심 사항으로 “모든 메서드의 시간 측정”으로 분리하고 핵심 관심 사항은 기존 회원가입, 조회 등의 목표를 유지하도록 한다.
aop 패키지를 생성하고 TimeTraceAop.java라는 공통 관심 사항에 대한 클래스를 작성한다.
package com.example.demoapp.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(* com.example.demoapp..*(..))") 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"); } } }
Java
복사
@Aspect 를 어노테이션을 통해서 AOP임을 등록하고 스프링 부트가 스캔하여 로드 할 수 있도록 @Component 로 등록한다.(Config를 통해서 Bean을 등록해도 됀다.
@Around 를 어노테이션을 통해서 시점을 지정했는데, @Before, @After, @Around 로 적용 시점을 지정 할 수 있다. 또한 execution 속성으로 적용될 대상을 지정 할 수 있다. pointcut 표현식을 통해서 세부적으로 지정 할 수 있다.
표현식은 다양하게 지정 할 수 있으므로 참고자료를 따라가서 필요한 부분을 작성
ex) execution([접근제어자] 반환타입 패키지.패키지.패키지.패키지.클래스.메소드(인자)
MemberService.java는 시간 측정으로 복잡해진 메소드를 핵심 관심 사항 로직만 남기고 복원한다.
package com.example.demoapp.service; import com.example.demoapp.domain.Member; import com.example.demoapp.repository.MemberRepository; import lombok.AllArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.List; import java.util.Optional; @Service @AllArgsConstructor public class MemberService { private final MemberRepository memberRepository; /** * 회원 가입 */ @Transactional public Long join(Member member) { //같은 이름 제외하는 경우 validateDuplicateMemberName(member); memberRepository.save(member); return member.getId(); } private void validateDuplicateMemberName(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); } }
Java
복사
Aspect로 지정한 메소드가 각 메소드들의 시간을 측정하는 것을 확인 할 수 있다.

AOP적용 전, 적용 후

스프링 AOP는 프록시(대리) 기반의 AOP 구현체이며, 스프링 Bean에만 AOP 적용 가능
원본 객체에 대한 코드 수정 없이, 런타임 시점에서 프록시 객체를 통해 부가적인 처리를 추가하기 위해서 사용된다.
프록시 객체는 원본 객체를 감싸고, 클라이언트가 프록시 객체를 호출할 때 원본 객체의 메소드를 호출하기 전에 추가적인 로직을 수행할 수 있도록 도와주는 역할을 한다.
Search
 | Main Page | Category |  Tags | About Me | Contact | Portfolio