@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface CheckTeam {
}
어노테이션을 생성하는 것은 간단하다.
다만 중요한게 Retention 과 Traget이다.
Retention(RetentionPolicy.RUNTIME)
→ 어노테이션을 런타임까지 유지하도록 설정한다. 즉, 실행 중 리플렉션 등을 통해 어노테이션 정보를 읽을 수 있다.
Target(ElementType.METHOD)
→ 이 어노테이션은 메서드에만 적용 가능하다는 뜻이다. 클래스, 필드 등 다른 요소엔 붙일 수 없다.
@Aspect
@Component
@RequiredArgsConstructor
@Slf4j
public class MyAspect {
@Around("@annotation(내어노테이션)")
public Object adviceMethod(ProceedingJoinPoint joinPoint, 내어노테이션 어노인스턴스) throws Throwable {
// 1. 전처리 로직 (Before)
Object result = joinPoint.proceed(); // 대상 메서드 실행
// 2. 후처리 로직 (After)
return result;
}
}
기본 틀은 이렇다 그러면 어떠한 어노테이션이 적용 되었는지 보자.
Aspect 이 클래스가 AOP 역할을 한다는 선언
Component 스프링 빈으로 등록
Around 대상 메서드 실행 전후로 로직 삽입
annotation(적용할 어노테이션) 어노테이션에서 적용
그리고 parameter을 좀 보면 ProceedingJoinPoint 라는 것이 있는데
이는 대상 메서드를 가로 챌 수 있게 한다고 생각하면된다.
joinPoint.proceed()는 가로챈 원래 메서드를 실제로 실행한다.
이러한 형태가 기본적인 형태이며 ProceedingJoinPoint를 통해서 아래와 같은 행동을 할 수있다.
`getArgs()` : 메서드에 전달된 매개변수들 가져오기
`getSignature()` : 메서드 이름, 클래스, 리턴타입 등의 정보 가져오기
`proceed()` : 실제로 그 메서드를 실행 시킴 (즉, 본래 로직으로 진입)
그렇다면 이제는 예시를 보자.
@Aspect
@Component
@RequiredArgsConstructor
@Slf4j
public class TeamCheckAspect {
//의존성 주입
private final TeamService teamService;
private final TeamMemberService teamMemberService;
private final MemberService memberService;
//어노테이션 설정
@Around("@annotation(checkTeam)")
public Object teamCheck(ProceedingJoinPoint joinPoint, CheckTeam checkTeam) throws Throwable {
Long teamId = null;
//joinPoint.getSignature()를 하면 메소드에 대한 정보를 가지고 있다.
//MethodSignature로 캐스팅하면, 메서드 이름, 파라미터 정보 등을 더 쉽게 가져올 수 있다.
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
//실제로 실행될 메서드 객체를 가져온다.
//이 method 객체로부터 파라미터 정보를 가져올 수 있다.
Method method = methodSignature.getMethod();
// 메서드의 파라미터중 id값을 가져오기 위한 작업
//getParameters()는 Parameter 객체 배열을 리턴하고,
//getName()은 각 파라미터의 변수 이름(예: teamId)을 가져온다
for (int i = 0; i < method.getParameters().length; i++) {
String parameterName = method.getParameters()[i].getName();
//파라미터의 이름이 teamId이면 Teamid를 할당 해준다.
if (parameterName.equals("teamId")) {
teamId = (Long) joinPoint.getArgs()[i];
break;
}
}
// team id가 없다면 예외 처리
if (teamId == null) {
throw new KanbanBoardTeamIdNotProvidedException(ExceptionMessage.Kanbanboard.KANBANBOARD_TEAM_ID_NOT_PROVIDED);
}
//Security Util을 이용해서 멤버의 id를 가져온다.
Long currentMemberId = SecurityUtil.getCurrentMemberId();
//Member을 조회한다.
Member member = memberService.getByMemberId(currentMemberId);
//Team을 조회한다.
Team team = teamService.getTeamById(teamId);
//Team Member을 조회한다. 아직 가입이 안된지 확인을 위함
TeamMember teamMember = teamMemberService.getTeamMemberByMemberAndTeam(member, team);
//team에 가입되어 있지 않다면 기존 메서드를 실행한다.
if (teamMember != null) {
return joinPoint.proceed();
}
//아니면 예외 처리
throw new SecurityException("접근 권한이 없습니다. " + " 팀에 소속되지 않았습니다.");
}
}
필자는 위의 코드와 같이 member을 Team에 초대하는데 기존 teamMember로 가입이 되어 있지 않다면 예외 처리를 하고 가입이 되어있다면 기존 메서드를 실행 할 수 있게 하였다.