티스토리 뷰

반응형

1. AOP(Aspect Oriented Programming, 관점 지향 프로그래밍)이란?


[ AOP(Aspect Oriented Programming, 관점 지향 프로그래밍)이란? ]

프로그래밍을 하다보면 공통적인 기능이 많이 발생한다. 이러한 공통 기능을 모든 모듈에 적용하기 위해 상속을 이용한다. 하지만 Java에서는 다중 상속이 불가능하며, 상속을 받아 공통 기능을 부여하기에는 한계가 있다.

예를 들어 우리가 개발한 API의 호출 시간을 측정하고 싶다고 하자. 이를 AOP없이 구현한다면 어떻겠는가? AOP를 적용하지 않는다면 중복 코드가 발생할 소지가 있고, 코드의 변경이 필요하면 여러 코드에 종속적으로 변경이 필요할 것이며, 핵심적인 비지니스 로직에 호출 시간 측정이라는 부수적인 로직이 추가되어 가독성과 효율성이 떨어지는 등의 문제가 발생할 수 있다. 이러한 문제점을 해결하기 위해 AOP(Aspect Oriented Programming, 관점 지향 프로그래밍)이 등장하게 되었다. AOP는 새로운 프로그래밍 패러다임이 아니라 OOP(Object Oriented Programming, 객체 지향 프로그래밍)를 돕는 보조적인 기술로, 핵심적인 관심 사항(Core Concern)과 공통 관심 사항(Cross-Cutting Concern)으로 분리시키고 각각을 모듈화 하는 것을 의미한다. 예를 들어 우리가 개발한 API중에 회원 가입 API가 있다면, 핵심적인 관심 사항은 회원

가입이라는 비지니스 로직이 될 것이고, 공통 관심 사항은 호출 시간 측정 로직이 될 것이다.

 

 

[ AOP(Aspect Oriented Programming)의 장점 ]

  • 공통 관심 사항을 핵심 관심사항으로부터 분리시켜 핵심 로직을 깔끔하게 유지할 수 있다.
  • 그에 따라 코드의 가독성, 유지보수성 등을 높일 수 있다.
  • 각각의 모듈에 수정이 필요하면 다른 모듈의 수정 없이 해당 로직만 변경하면 된다.
  • 공통 로직을 적용할 대상을 선택할 수 있다

 

2. 패키지 기반, 어노테이션 기반으로 AOP 사용해보기


[ AOP 사용해보기 ]

다른 요소들과 마찬가지로, AOP를 적용하기 위해서는 먼저 의존성을 추가해주어야 한다.

implementation 'org.springframework.boot:spring-boot-starter-aop'

 

AOP 의존성을 추가하고 빌드를 하였으면 AOP를 활성화하겠다는 어노테이션을 추가해주어야 한다. SpringBoot의 애플리케이션 클래스에 @EnableAspectJAutoProxy 어노테이션을 추가해주도록 하자.

@EnableAspectJAutoProxy
@SpringBootApplication
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

}

 

위의 두 과정을 거치면 AOP를 적용하기 위한 준비가 끝난 것이다. 어디에 AOP를 적용하지 말고, 실행 시간을 측정하기 위한 Aspect 클래스부터 개발해보도록 하자.

@Aspect
@Component
@Log4j2
public class ExecutionTimeAop {

	// 모든 패키지 내의 controller package에 존재하는 클래스
	@Around("execution(* *..controller.*.*(..))")
	public Object calculateExecutionTime(ProceedingJoinPoint pjp) throws Throwable {
		// 해당 클래스 처리 전의 시간
		StopWatch sw = new StopWatch();
		sw.start();

		// 해당 클래스의 메소드 실행
		Object result = pjp.proceed();

		// 해당 클래스 처리 후의 시간
		sw.stop();
		long executionTime = sw.getTotalTimeMillis();

		String className = pjp.getTarget().getClass().getName();
		String methodName = pjp.getSignature().getName();
		String task = className + "." + methodName;

		log.debug("[ExecutionTime] " + task + "-->" + executionTime + "(ms)");

		return result;
	}

}

 

우선 Aop 클래스로 설정하기 위해 @Aspect 어노테이션을 추가해주었으며, Spring의 빈으로 등록하기 위해 @Component 어노테이션을 추가해주었다. 그리고 우리가 하고자 하는 것은 모든 API의 실행 시간을 측정해주는 것이므로 StopWatch를 생성하여 측정을 시작하였다. 그리고 pjp의 proceed를 통해 실제 핵심 로직을 실행하여 Object 클래스로 메소드의 결과를 받았다. 이후에 StopWatch를 중단하여 실행 시간을 밀리세컨드로 계산하여 로그를 출력하고 함수를 종료시키고 있다.

 

이제 위와 같은 기능을 적용할 지점을 선정해야 하는데, 크게 패키지 기반으로 하는 방법과 어노테이션 기반으로 하는 방법이 있다.

 

 

패키지기반으로 AOP 적용하기

먼저 Controller 패키지에 해당 기능을 적용하기 위해서는 다음과 같은 @Around를 적용해줄 수 있다.

@Aspect
@Component
@Log4j2
public class ExecutionTimeAop {

	// 모든 패키지 내의 controller package에 존재하는 클래스
	@Around("execution(* *..controller.*.*(..))")
	public Object calculateExecutionTime(ProceedingJoinPoint pjp) throws Throwable {
		// 해당 클래스 처리 전의 시간
		StopWatch sw = new StopWatch();
		sw.start();

		// 해당 클래스의 메소드 실행
		Object result = pjp.proceed();

		// 해당 클래스 처리 후의 시간
		sw.stop();
		long executionTime = sw.getTotalTimeMillis();

		String className = pjp.getTarget().getClass().getName();
		String methodName = pjp.getSignature().getName();
		String task = className + "." + methodName;

		log.debug("[ExecutionTime] " + task + "-->" + executionTime + "(ms)");

		return result;
	}

}

 

 

 

어노테이션 기반으로 AOP 적용하기(클래스 레벨, 메소드 레벨)

반면에 어노테이션 기반으로 적용하기 위해서는 AOP를 처리하기 위한 어노테이션을 다음과 같이 만들어주어야 한다.

(커스텀 어노테이션 만들기에 대해 이해가 여기를 참고해주세요!) 

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface ExecutionTimeChecker {
}

 

 

위에서 @Target은 어노테이션이 적용될 레벨을 의미한다. 이번에는 클래스 또는 메소드에 적용하고자 TYPE, METHOD를 추가해주었다.

그 다음에는 해당 어노테이션이 적용되도록 @Around를 수정해야 하는데, 다음과 같이 수정해주면 된다.

@Aspect
@Component
@Log4j2
public class ExecutionTimeAop {

    @Around("@within(com.mang.atdd.membership.aop.ExecutionTimeChecker)")
	public Object calculateExecutionTime(ProceedingJoinPoint pjp) throws Throwable {
		// 해당 클래스 처리 전의 시간
		StopWatch sw = new StopWatch();
		sw.start();

		// 해당 클래스의 메소드 실행
		Object result = pjp.proceed();

		// 해당 클래스 처리 후의 시간
		sw.stop();
		long executionTime = sw.getTotalTimeMillis();

		String className = pjp.getTarget().getClass().getName();
		String methodName = pjp.getSignature().getName();
		String task = className + "." + methodName;

		log.debug("[ExecutionTime] " + task + "-->" + executionTime + "(ms)");

		return result;
	}

}

 

그리고 해당 메소드를 호출해보면 실행 시간을 측정하는 AOP가 클래스 레벨에는 물론 메소드 레벨까지 정상적으로 실행됨을 확인할 수 있다.

 

 

 

 

만약 AOP를 적용하지 않은 상태에서 실행 시간 측정 단위를 밀리세컨드가 아닌 세컨드로 변경한다고 하면 어떻게 해야 할까? AOP를 적용하지 않았다면 중요하지도 않은 비지니스 로직도 아님에도불고하고, 관련 로직의 모든 코드를 수정했어야 할 것이다. 하지만 AOP를 적용함으로써 핵심 로직에 대한 수정 없이 편리하기 이를 처리할 수 있게 되었다.

 

 

 

 

관련 포스팅

  1. AOP(Aspect Oriented Programming, 관점 지향 프로그래밍)의 이해 - (1/3)
  2. AOP(Aspect Oriented Programming, 관점 지향 프로그래밍)의 개념 및 사용 방법 예제 코드 - (2/3)
  3. Spring에서 AOP Proxy 구현 방법과 @EnableAspectJAutoProxy의 proxyTargetClass - (3/3)

 

 

 

반응형
댓글
댓글쓰기 폼
  • ryumodern 안녕하세요! 블로그 글 재밌게 보고 있습니다

    개인 프로젝트를 진행하는 중이고 슬로우 쿼리나 잘못된 비즈니스 로직을 파악하기 위해 annotation 방식의 시간 측정을 하고 있습니다
    앞으로도 method가 계속 늘어날 예정인데 annotation을 method level이 아닌 class level에 적용하는 방법이 있는지 혹시 아시고 계신지 궁금합니다
    2021.09.09 15:16 신고
  • 망나니개발자 안녕하세요~! 먼저 방문해주셔서 감사합니다! 당연히 클래스 레벨에도 어노테이션 기반으로 적용할 수 있고, 해당 내용들 반영되도록 글 수정해서 저녁에 올리겠습니다:) 2021.09.09 16:11 신고
  • ryumodern 와 정말 감사합니다..! 프로젝트에 잘 적용되네요 진짜 큰 도움 됐습니다
    양질의 글 써주셔서 감사해요! 블로그 자주 들르겠습니다 :)
    2021.09.09 21:05 신고
  • 망나니개발자 도움이 되셨다니 뿌듯하네욤ㅎㅎ 작업하시다가 또 도움 필요한부분 있으면 말씀해주세용:) 2021.09.09 21:33 신고
반응형
공지사항
Total
1,585,305
Today
1,782
Yesterday
5,473
TAG more
«   2021/10   »
          1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
31            
글 보관함