AOP 공부하기
AOP 파헤치는 이유
내가 마주한 문제들
개념설명
제일 먼저 보이는 @Around는 어드바이스입니다.
앞서 설명드린것 처럼 어드바이스는 애스펙트가 "무엇을", "언제" 할지를 의미하고 있습니다.
여기서 "무엇"은 calculatePerformanceTime() 메소드를 나타냅니다.
그리고 "언제"는 @Around가 되는데, 이 언제 라는 시점의 경우 @Around만 존재하지 않고 총 5가지의 타입이 존재합니다.
예를 들어 타겟 메소드의 이전 시점에서만 어드바이스 메소드를 수행하고 싶다면,
@Before("포인트컷 표현식")
public void 어드바이스메소드() {
....
}
식으로 작성하시면 됩니다.
여기서 주의하실 점은 @Around의 경우 반드시 proceed() 메소드가 호출되어야 한다는 것입니다.
proceed() 메소드는 타겟 메소드를 지칭하기 때문에 proceed 메소드를 실행시켜야만 타겟 메소드가 수행이 된다는것을 잊으시면 안됩니다.
그런데 @After를 사용했을 때 두 가지 문제점이 있었다.
첫 번째는 요청파라미터는 joinPoint를 통해서 가져올 수 있었지만 내가 응답한 리턴파라미터를 가져오기가 애매했다.
두 번째는 컨트롤러를 통한 서비스에서 예외가 발생했을 때 로그를 정확히 기록하지 못했다.
그래서 정상처리가 됐을 때와 예외가 발생 했을 때를 구분할 수 있는 방법을 사용했다.
AfterReturning, AfterThrowing
@AfterReturning, @AfterThrowing 이 두 개의 어노테이션을 사용하면 @After 어노테이션을 사용했을 때 보다 더 디테일하게 AOP를 구현할 수 있다. 정상적으로 로직이 수행되어 리턴값이 있을 때와 중간에 예외가 발생했을 때 각각 처리가 가능하다.
커스텀 어노테이션 AOP 예제
출처 : https://huisam.tistory.com/entry/springlogging
커스텀 AOP 사용해보기
gladel 추가
implementation 'org.springframework.boot:spring-boot-starter-aop'
인터페이스 추가
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@interface LogExecutionTime {
}
Target을 메서드 어노테이션으로 지정해주고, 런타임에 동작할 수 있게 정책을 만들었어요~!
위 어노테이션이 붙게 되면, 실행시간을 측정하고 로그에 남겨보도록 할 거에요!
import lombok.extern.log4j.Log4j2;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
import org.springframework.util.StopWatch;
@Component
@Aspect // Aspect 를 꼭 명시!
@Log4j2
class LogAspect {
@Around("@annotation(LogExecutionTime)") // 해당 어노테이션에 대해서
public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
// 컨트롤러 실행전 타이머 설정
StopWatch stopWatch = new StopWatch();
stopWatch.start();
Object proceed = joinPoint.proceed();
stopWatch.stop();
log.info(stopWatch.prettyPrint());
return proceed;
}
}
StopWatch 공식 문서 : https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/util/StopWatch.html
1 sec = 1,000 ms = 1,000,000 μs = 1,000,000,000 ns 입니다.
그리고 원하는 곳에 @LogExecutionTime 를 붙이면 된다.
에러 발생
java.lang.IllegalArgumentException: error Type referred to is not an annotation type
같은 경로에 있기때문에 특별히 경로까지 풀로 적지 않았는데 다른 경로에 있다면
예를들어 TestAnnotation이 com.example.demo.annotation.TestAnnotation 요런 경로에 있고 TestAspect 클래스가
다른 경로에 있다면 @annotation(com.example.demo.annotation.TestAnnotation) 요렇게 풀로 적어야 함
AOP가 적용되지 않는 현상
질문
오버로딩한 메서드에서 적용이 되질 않음
a (1) retun this.a(1,2,3)
a(1,2) return this.a(1,2,3)
@@@여기 어노테이션
a(1,2,3) return result
a(1,2,3) 으로 직접 호출한 경우에만 인식이 됩니다.
a(1) , a(1,2) 로 호출하니 인식이 안되구요
근데 또 모든 오버로딩 부분에 어노테이션 달면 잘 인식됩니다
AOP, 커스텀 어노테이션, 오버로딩 이 키워드들이 아니었다.
AOP, 내부 호출 이게 키워드였다.
답 출처 : https://velog.io/@hyun6ik/%ED%94%84%EB%A1%9D%EC%8B%9C%EC%99%80-%EB%82%B4%EB%B6%80-%ED%98%B8%EC%B6%9C
AOP는 프록시 패턴인데 (임시자) this. 이렇게 내부 메서드를 호출하는건 실제 객체를 호출하는거라 인식을 못하는 거라 한다.
프록시패턴??? 실제 객체 호출?? 이게 뭔데...요
토비의 스프링 책을 또 열심히 파헤쳐보도록 하자