스프링 aop
스프링 AOP는 Aspect Oriented Programming의 약자이며, 관점 지향 프로그래밍입니다.
관점 지향 프로그래밍의 "관점 지향이란" 크게 핵심적인 관점과 부가적인 관점으로 나누어서 각각 모듈화를 하겠다는 것입니다.(관심사 분리)
예를 들어보자.
한 개발자가 개발 중에 있는데, 갑자기 모든 호출되는 메서드의 소요시간을 알고 싶다며, 모든 메서드에 소요시간을 구하는 로직을 구현을 해야 한다..
메서드가.... 1만 개라면..? 할 수야 있겠지만 엄청나게 오래 걸릴 것이다..
이런 식으로 개발을 하게 된다면..?
- 중복 코드 발생 -> 모든 코드에 똑같이 반복되는 코드가 생긴다. 흩어진 관심사(Crosscutting Concerns)
- 핵심 로직과 부가적인 로직으로 인하여 service단에서 핵심 로직에 말고도 또 부가적인 로직에도 신경을 써야 함.
아까 위에서 말했다시피 핵심 관심사와 부가적인 관심사를 분리를 하여 모듈화를 하고 관리를 하면
좀 더 객체지향스러워(?) 진다.
AOP의 용어 정리
- Target
- 핵심기능을 가지고 있는 모듈로써 부가기능을 부여할 대상이다.(핵심 로직이라고 생각하면 된다.)
- Advice
- 실질적으로 부가기능을 담은 구현체이다(실질적인 부가기능이다)
- PointCut
- Advice를 적용할 타깃의 메서드를 선정하는 방법(부가기능을 "어디에 적용할 것인지"->모든핵심 로직에 넣을 것인지 해당원하는 핵심로직에 넣을 것인지)
- JoinPoint
- Advice 적용될 수 있는 위치 (부가기능을 핵심 로직이 실행 전, 실행 후 등등 Adivce를 실행)
- Aspect
- Adivce+PointCut을 합친 개념이다.
- 싱글톤으로 존재한다.
- AOP 개념을 적용하면 핵심기능 코드 사이에 침투된 부가기능을 독립적인 애스펙트로 구분해 낼 수 있다.
- 구분된 부가기능 애스펙트를 런타임 시에 필요한 위치에 동적으로 참여하게 할 수 있다.
- Proxy
- 프락시 기반이다.
- 스프링에서는 Aop동작을 Proxy가 가로채어서 Advice의 부가기능을 실행 후 Target의 핵심기능을 실행한다.
- Weaving
- 포인트 컷에 의해서 결정된 타깃의 조인 포인트에 부가기능(advice)을 삽입하는 과정을 뜻한다.
- AOP가 핵심기능(타깃)의 코드에 영향을 주지 않으면서 필요한 부가기능(advice)을 추가할 수 있도록 해주는 핵심적인 처리과정이다.
joinPoint
- @Around (메서드 실행 전후)
- 타깃 메소드 호출전과 후에 Advice기능을 수행한다.
- @Before (이전)
- Advice 타깃 메소드가 호출되기 전에 Advice 기능을 수행
- @After (이후)
- 타깃 메소드의 결과에 관계없이(즉 성공, 예외 관계없이) 타겟 메소드가 완료 되면 Advice기능을 수행
- 무조건 실행된다고 보면된다.
- @AfterReturning (정상적 반환 이후)
- 타겟 메소드가 성공적으로 결과값을 반환 후에 Advice기능을 수행
- @AfterThrowing (예외 발생 이후)
- 타겟 메소드가 수행 중 예외를 던지게 되면 Advice기능을 수행
PointCut 종류
- args()
- 메소드의 인자가 타겟 명세에 포함된 타입일 경우
- ex) args(java.lang.Long)
- @args()
- 메소드의 인자가 타겟 명세에 포함된 어노테이션 타입을 갖는 경우
- execution()
- 접근 제한자, 리턴 타입, 인자 타입, 클래스/인터페이스, 메서드명, 파라미터 타입, 예외 타입 등을 전부 조합 가능한 가장 세심한 지정자
- 대부분 이것을 사용하며, 세세하게 다 적용될 수 있음.
- within()
- execution 지정자에서 클래스/인터페이스까지만 적용된 경우
- 즉, 클래스 혹은 인터페이스 단위까지만 범위 지정이 가능하다.
- exectuion 지정자보다는 덜 세세함.
- @within()
- 주어진 어노테이션을 사용하는 타입으로 선언된 메서드
- this()
- 타깃 메소드가 지정된 빈 타입의 인스턴스인 경우
- target()
- this와 유사하지만 빈 타입이 아닌 타입의 인스턴스인 경우
- @target()
- 타깃 메소드를 실행하는 객체의 클래스가 타겟 명세에 지정된 타입의 어노테이션이 있는 경우
- @annotation
- 타겟 메서드에 특정 어노테이션이 지정된 경우
- 내가 지정한 부분만 어노테이션을 사용하여서 Advice를 적용할 수 있음.
위에 예를 들었다시피 메서드 별로 시간 측정을 하기를 원한다.
모든 메서드는 아니고 Controller, Service Layer의 Method들의 시간 측정을 원한다.
하는 방법은 여러 가지이다 @annotation을 지정하여 할 수도 있지만, @annotation은 특정 어떠한 타깃에만 소수로 적용될 때 좋은 것 같다.
내가 적용한 방법은 타깃의 핵심 로직 메서드를 호출 전에 time을 찍고 핵심로직 수행 후에도 time을 찍어서 측정을 할 것이기 때문에 @Around advice를 적용을 하여서 구현한다.
@Aspect
@Component
@Slf4j
public class TimeTraceAop {
//컨트롤러 포인트컷
@Pointcut("within(com.example.yoonFood.controller..*)")
private void getControllerTime(){
}
//서비스 포인트컷
@Pointcut("within(com.example.yoonFood.service..*)")
private void getServiceTime(){
}
@Around("getControllerTime() || getServiceTime()")
public Object methodControllerTime(ProceedingJoinPoint proceedingJoinPoint){
Object result = null;
long start = System.currentTimeMillis();
String methodName = proceedingJoinPoint.getSignature().getName();
log.info("Start: 실행 메서드 :{} ",methodName);
try {
result = proceedingJoinPoint.proceed();
long end = System.currentTimeMillis();
log.info("End : 실행 메서드 :{} ,수행시간 : {}",methodName ,end-start+"ms" );
} catch (Throwable e) {
throw new RuntimeException(e);
}
return result;
}
}
측정을 하여서 log를 띄우는 건 개발자 스타일마다 다른 것이고.. 나는 메서드명을 띄었다 아니면 ToString을 이용하여서 해도 된다 (이건 개발자 맘!)
@Around의 포인트 컷은 @PointCut으로 위에화 같이 따로 분리하여서 설정하여도 되고
ex) @Around("execution(com.example.yoonFood..*)")등으로 바로 설정해도 된다.
포인트 컷 지정자는 여러 가지 조합이 많기 때문에 자기가 원하는 스타일대로 구글링을 해서 사용을 하면될 것 같다..
그리고 측정 중에 Controller와 Service Layer는 연관관계로 묶여있기 때문에(의존성이 주입되어 있기 때문에)
대부분의 Controller를 호출을 하면, Service의 method를 호출을 한다.
그래서 Controller 호출 -> Service호출 -> Service 끝-> Controller끝 이런 식으로 진행이 된다.
추가로
구글링을 해서 인터넷에 자료를 검색을 하는 중에 대부분이 @Around,@Before,@After와 같이 joinPoint를
Advice라고 설명을 하는 것 같다.
Advice는 부가기능을 포함한 그 자체인데... JoinPoint 뜻도 Advice를 어디에 적용을 하는가에.. 뜻인데..
이것 때메 이해 하기가 쫌 어려웠다
아님 말고 이상 끝!
Reference