Notice
Recent Posts
Recent Comments
Link
«   2026/08   »
1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31
Tags
more
Archives
Today
Total
관리 메뉴

나의 개발 일상 기록

AOP 적용하여 부가적인 로직 제거 본문

ETC

AOP 적용하여 부가적인 로직 제거

느린 거북이 2021. 6. 29. 12:59

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);
}

 

 

Comments