@PostMapping
public void post(@ApiIgnore final Authentication authentication) {
if (authentication == null)
throw new UserTokenNotExistException();
User user = ((SecurityUser) authentication.getPrincipal()).getUser();
...
}
로그인이 필요한 API 마다 위와 같은 코드가 중복적으로 적용이 돼있었다.
AOP 적용을 통해 어노테이션 적용만으로 중복 코드를 제거하고자 했다.
package com.recipe.app.src.common.aop;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface LoginCheck {
}
커스텀한 어노테이션을 생성
@Retention 으로 어노테이션이 언제까지 살아남을 것인지 정하고, @Target 으로 어노테이션이 어디에 적용될지를 정함
@Target 은 메소드 범위로 적용@Retention 을 런타임 시까지 유지@Component
@Slf4j
@Aspect
public class LoginCheckAspect {
@Around("@annotation(com.recipe.app.src.common.aop.LoginCheck)")
public Object loginCheck(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null || !(authentication.getPrincipal() instanceof SecurityUser)) {
throw new UserTokenNotExistException();
}
User user = ((SecurityUser) authentication.getPrincipal()).getUser();
log.info("Login User Id : " + user.getUserId());
Object[] args = Arrays.stream(proceedingJoinPoint.getArgs())
.map(arg -> {
if (arg instanceof User) {
return user;
}
return arg;
})
.toArray();
return proceedingJoinPoint.proceed(args);
}
}
@Aspect 어노테이션으로 부가기능을 제공하는 Aspect 클래스 작성
포인트컷(Pointcut)과 어드바이스(Advice)로 구성된 어드바이저(Advisor)의 생성을 편리하게 해주는 기능을 가짐
Aspect 클래스를 Aspect 로 사용하려면 Bean 으로 등록해야하므로 @Component 작성 필요
@Around("@annotation(com.recipe.app.src.common.aop.LoginCheck)") 설정으로 커스텀한 어노테이션에 AOP 적용
Advice 정의하는 어노테이션으로는 @Before, @After, @Around, @AfterReturning, @AfterThrowing 존재
@Before: 타겟 호출 전에 공통 기능을 실행@After: 예외 발생 여부에 상관없이 타겟 실행 후 공통 기능을 실행@AfterReturning: 타겟이 예외 없이 실행된 이후에 공통 기능을 실행@AfterThrowing: 타겟이 실행 도중 예외 발생한 경우에 공통 기능을 실행@Around: 타겟 실행 전, 후 또는 예외 발생 시점에 공통 기능을 실행@Around는 다른 포인트컷과 달리 ProceedingJointPoint라는 구현체를 사용하는데, 이 구현체를 통해 자바의 리플렉션 기능을 사용하여 타겟 메소드 호출 가능
@Around 를 사용 @PostMapping
@LoginCheck
public void post(@ApiIgnore User user) {
...
}
적용할 컨트롤러 API 메소드에 커스텀하게 작성한 어노테이션 @LoginCheck 를 추가
@PostMapping 위가 아니라 적용할 특정 메소드의 바로 위에 어노테이션을 적용해줘야 함AOP 적용을 통해 응답값으로 넘겨줄 User 클래스를 메서드의 매개변수로 받도록 설정