나의 개발 일상 기록
AOP 적용하여 부가적인 로직 제거 본문
AOP 적용하여 부가적인 로직 제거하기(SPRING BOOT)
1. AOP를 사용하는 이유
spring에서는 AOP라는 기능이 존재합니다. 흩어져있는 공통 기능들에 대해서 하나의 관점으로 바라보도록 해주며 사용자가 원하는 지점에서 실행할 수 있도록 해줍니다. AOP 사용 시 소스에 대한 관리가 더 용이해지는 장점이 있습니다.
간단한 메소드 성능 검사, 트랜잭션 처리, 예외 반환, 로깅, 인증, 권한 등 AOP를 통해 구현할 수 있습니다. 본문에서는 커스텀 어노테이션을 활용하여 해당 어노테이션이 명시된 메소드를 실행하기 로그인 권한을 확인하는 기능을 구현해보겠습니다.
2. AOP를 사용하기 전 사전 작업
AOP를 사용하기 전에 pom.xml에 spring-boot-starter-aop 을 의존성 주입을 해야합니다.
| <!-- AOP --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> |
3. session에 저장된 로그인 정보 저장 및 조회 (SessionUtil.java)
해당 파일은 로그인 시 로그인 정보를 session에 저장 후 특정 기능 이용 시 session에 있는 로그인 정보를 조회하여 로그인 권한 절차에 영향을 주기 위한 파일로써 역할을 합니다.
public class SessionUtil {
private static final String LOGIN_USER_ID = "LOGIN_USER_ID";
private static final String LOGIN_ADMIN_ID = "LOGIN_ADMIN_ID";
// 인스턴스화 방지
private SessionUtil() {}
// 로그인 한 회원정보를 session에 저장한다.
public static void setLoginUserInfo(HttpSession session, String loginId) {
session.setAttribute(LOGIN_USER_ID, loginId);
}
// 로그인한 회원의 로그인ID를 세션에서 꺼낸다.
public static String getLoginUserId(HttpSession session) {
return (String) session.getAttribute(LOGIN_USER_ID);
}
// 로그인 한 관리자정보를 session에 저장한다.
public static void setLoginAdminInfo(HttpSession session, String loginId) {
session.setAttribute(LOGIN_ADMIN_ID, loginId);
}
// 로그인한 관리자의 로그인ID를 세션에서 꺼낸다.
public static String getLoginAdminId(HttpSession session) {
return (String) session.getAttribute(LOGIN_ADMIN_ID);
}
}
4. 로그인 타입을 설정 (LoginCheck.java)
해당 파일은 로그인 시 사용자의 타입을 선언합니다.
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface LoginCheck {
UserType type();
public static enum UserType{
USER, ADMIN
}
}
- @Retention Annotation
Retention은 해당 어노테이션이 언제까지 유지할지 알려주는 어노테이션입니다.
해당 어노테이션에는 3가지 속성이 있습니다.
1. SOURCE
- 커스텀 어노테이션을 주석처럼 사용 시 사용이 되며, 컴파일을 하여 생성된 .class에 해당 어노테이션정보가 사라집 니다. 즉, 소스 코드까지만 유지를 합니다.(= 컴파일 후 해당 어노테이션은 사라집니다.)
2. CLASS
- 해당 어노테이션의 기본값은 class타입입니다. 컴파일 한 .class파일에서도 유지를 합니다.
즉, 런타임 시 클래스를 메모리로 읽어오면 해당 정보는 사라집니다.
3. RUNTIME
- 클래스를 메로리에 읽어왔을 때까지 유지를 시켜줍니다. 코드에서 이 정보를 바탕으로 특정 로직을
실행할 수 있습니다.
- @Target
Target은 Java complier가 annotation이 어디에 적용될지 경정하기 위해 사용합니다.
종류는 다음과 같습니다.
ElementType.PACKAGE : 패키지 선언
ElementType.TYPE : 타입 선언
ElementType.ANNOTATION_TYPE : 어노테이션 타입 선언
ElementType.CONSTRUCTOR : 생성자 선언
ElementType.FIELD : 멤버 변수 선언
ElementType.LOCAL_VARIABLE : 지역 변수 선언
ElementType.METHOD : 메서드 선은
ElementType.PARAMETER : 전달인자 선언
ElementType.TYPE_PARAMETER : 전달인자 타입 선언
ElementType.TYPE_USE : 타입선언
5. 로그인 check 로직을 구현한 class 생성 (LoginCheckAspect.java)
실제 @LoginCheck 어노테이션의 type에 따라 일반 유저, 관리자 유저 인지 확인 후 id값을 JoinPoint의 Argument에 설정하여 이후 핵심 비즈니스 로직을 수행합니다.
@Component
@Aspect
@Log4j2
public class LoginCheckAspect {
@Around("@annotation(com.market.server.aop.LoginCheck) && @annotation(loginCheck)")
public Object adminLoginCheck(ProceedingJoinPoint proceedingJoinPoint, LoginCheck loginCheck) throws Throwable{
HttpSession session = ((ServletRequestAttributes)(RequestContextHolder.currentRequestAttributes())).getRequest().getSession();
String id = null;
int idIndex = 0;
String userType = loginCheck.type().toString();
switch (userType) {
case "ADMIN":{
id = SessionUtil.getLoginAdminId(session);
break;
}
case "USER":{
id = SessionUtil.getLoginUserId(session);
id = "gkrthdz";
break;
}
}
if(id == null) {
throw new HttpStatusCodeException(HttpStatus.UNAUTHORIZED, "로그인한 id값을 확인해주세요.") {};
}
Object[] modifiedArgs = proceedingJoinPoint.getArgs();
if(proceedingJoinPoint.getArgs() != null)
modifiedArgs[idIndex] = id;
return proceedingJoinPoint.proceed(modifiedArgs);
}
}
- @Component
Component 어노테이션의 경우 개발자가 직접 생성한 Class의 경우에 사용이 되며, Class를 spring에게 자동으로 인스턴스 생성 후 등록(Bean) 역할을 해줍니다.
- @Aspect
Aspect의 경우 해당 Class가 횡단관심사(부가기능) Class임을 알려주는 어노테이션입니다. Aspect가 부여되었다고 해서 자동으로 Bean으로 등록되는 것이 아니므로 따로 Bean으로 등록을 해주는 작업이 필요합니다.
- @Before (이전)
- 어드바이스 타겟 메소드가 호출되기 전에 어드바이스 기능을 수행
- @After (이후)
- 타겟 메소드의 결과에 관계없이(즉 성공, 예외 관계없이) 타겟 메소드가 완료 되면 어드바이스 기능을 수행
- @AfterReturning (정상적 반환 이후)
- 타겟 메소드가 성공적으로 결과값을 반환 후에 어드바이스 기능을 수행
- @AfterThrowing (예외 발생 이후)
- 타겟 메소드가 수행 중 예외를 던지게 되면 어드바이스 기능을 수행
- @Around (메소드 실행 전후)
- 어드바이스가 타겟 메소드를 감싸서 타겟 메소드 호출전과 후에 어드바이스 기능을 수행
6. LoginCheck 어노테이션의 활용
아래의 메서드처럼 @LoginCheck Annotation 선언 시 해당 메서드 진입 전 일반 유저인지 검사 후 맞다면 해당 메서드를 수행하게 됩니다.
@GetMapping("myInfo")
@LoginCheck(type = UserType.USER)
public UserInfoResponse userInfo(HttpSession session) {
String loginId = SessionUtil.getLoginUserId(session);
UserDTO userDTO = userService.getUserInfo(loginId);
return new UserInfoResponse(userDTO);
}
'ETC' 카테고리의 다른 글
| SockJs을 이용한 1:1 채팅 구현하기 (0) | 2021.07.07 |
|---|---|
| FCM서버 비동기(Asynchronous)로 요청 및 응답 (0) | 2021.07.06 |
| 대용량 트래픽 환경에서 서버 부하를 줄이기 위한 캐싱 적용 (0) | 2021.07.05 |
| REST API (0) | 2021.07.05 |
| 서버확장을 위한 SCALE UP vs SCALE OUT (0) | 2021.06.28 |