회사에서 자주 사용되는 AOP를 회사 코드를 통해 공부를 진행해보자.
관점지향 프로그래밍인 AOP를 활용할 수 있는 어노테이션이다.
Proxy 패턴을 활용한다.
@Around("execution(* com.package1.package2.api.oauth2.endpoint.LoginEndpoint.login(..))")
public Object saveAccessLog(ProceedingJoinPoint joinPoint) throws Throwable {
Object[] args = joinPoint.getArgs();
try {
Object proceed = joinPoint.proceed();
// Exception이 없으면 로그인이 성공
saveSuccessLog(args[0]);
return proceed;
} catch (Exception e) { // Exception이 발생하면 로그인이 실패
saveFailLog(e, args);
throw e;
} finally {
accessLogSaveCommand.execute();
}
}
예시 코드
여기서 이제 ProceedingJoinPoint를 활용하여, args를 조정도 할 수 있다.
또한, @Before, @PointCut 등도 있고, 그 내에 있는 것들은 execution 등등이 있다. within 이런 것들도, 이런 것들을 사용할 때에는 정규식을 사용할 수도 있다. 조금 더 효율을 증진시키기 위해
@Before("(@within(com.package1.package2.api.aspect.AopController) || @within(com.package1.package2.api.aspect.RestAopController))"
+ " && excludeLoginPackage()")
public void before() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
String userId = authentication.getName();
if (isAnonymous(userId) || UserContextHolder.isExist(userId)) {
return;
}
setContext(userId, authentication);
}
예시 코드
보면 within을 || 로 연결하기도 하는 것을 볼 수 있고, Class를 명시하는 것도, 또한, 아래의 메서드를 포함해서 진행하는 것도 있다.
쨌든 Aspect한 메서드 이전에 실행되는 것이기에, 아무런 인자가 없는 것을 볼 수 있다.
@Pointcut("!execution(* com.package1.package2.api.oauth2.endpoint.LoginEndpoint.*(..))")
private void excludeLoginPackage() {
// Pointcut for excluding package
}
흠.. 근데, PointCut이 이해가 안된다. 이를 이해하기 이전에 다음과 같은 그림을 접하게 되었고, JoinPoint에 대한 이해도가 부족하다는 것을 알게되었다.


그래서 Aspect, Join Point, Point Cut 이 무엇일까?
Aspect, Join Point, Advice, Pointcut, Target을 각각 하나씩 봐보자.
Aspect로 모아 관리하면 코드가 깔끔해집니다.Aspect가 적용될 수 있는 특정 지점입니다.Join Point가 될 수 있습니다.즉, JoinPoint는 정말 실행 시점을 이야기하는 것 같다. (어떤 메서드가 실행되면 진행된다. 이런 게 아니라 정말 Proxy가 동작하는 그 순간)
Aspect가 적용되는 Join Point에서 실행되는 구체적인 행동입니다.Join Point에서 실제로 실행되는 코드입니다. 어떤 작업을 하겠다는 '지시' 같은 것이라고 보면 됩니다.Advice입니다.즉, 그냥 메서드에 짜놓은 로직들을 Advice라고 칭할 수도 있을 것 같음
Join Point를 선별하는 규칙이나 표현식입니다.Aspect를 적용할 Join Point를 선택하는 조건입니다.Join Point를 선택할 수 있습니다.PointCut은 이제, Trigger를 의미하는 것 같음
Aspect가 적용되는 실제 객체입니다.Aspect의 기능이 적용될 대상 객체입니다.Aspect가 로그를 기록하는 기능을 제공한다고 할 때, 실제로 로그를 남기고 싶은 객체가 Target입니다.GPT 센세가 이야기했다.
PointCut은 그 시점을 결정하는 것, 그냥 JoinPoint의 조건을 결정하는 것이라고 생각하면 된다고 한다.
그렇기 때문에, 이를 타 Aspect Annotation에서 excludeLoginPackage를 사용해 조건으로 사용할 수 있는 것이다.
또한, @Around는 Before, After를 합쳐놓은 것이라고 보면 되는데, 그냥 아무 시점에서 진행할 수 있는 것이고, 여기서 값을 반환하게 되면, Proxy가 적용된 메서드의 반환값에서 Customizing하게 나가기 때문에, 이 부분에서 성능적인 우려와(아무래도 한번 더 거치게 되고, Proxy를 찾는데에도 조금 resource가 들지 않을까 싶다.) 보이지 않는 로직이 존재해 떡칠을 하면 유지보수가 어려워질 수 있다는 단점이 있다.
https://velog.io/@shining_dr/Annotation%EA%B8%B0%EB%B0%98-%EC%8A%A4%ED%94%84%EB%A7%81-AOP
https://ittrue.tistory.com/233
https://wpunch2000.tistory.com/22
https://hellomooneekim.netlify.app/%ED%9A%A1%EB%8B%A8%EA%B4%80%EC%8B%AC%EC%82%AC/
https://f-lab.kr/insight/understanding-spring-aop