코딩하는 오징어

Spring boot @Annotation으로 AOP적용하기 본문

Framework/Spring

Spring boot @Annotation으로 AOP적용하기

코딩하는 오징어 2018. 3. 4. 15:19
반응형

AOP를 적용하기 전에 AOP가 무엇인지에 대해 먼저 알아보자. 

AOP란 Aspect Oriented Programming의 약자로써 바라보는 관점을 기준으로 프로그래밍하는 기법을 말한다. 문제를 해결하기 위해 핵심 관심 사항과 전체에 적용되는 공통 관심 사항을 기준으로 프로그래밍 함으로써 공통 모듈을 여러 코드에 쉽게 적용할 수 있도록 도와준다.


AOP의 주요 용어


Joinpoint : Advice를 적용 가능한 지점을 의미. 메서드 호출, 필드 값 변경등이 Joinpoint에 해당

Pointcut : Joinpoint의 부분 집합으로서 실제로 Advice가 적용되는 Joinpoint를 나타낸다. 스프링에서는 정규 표현식이나 AspectJ의 문법을 이용하여 Pointcut을 정의할 수 있음.

Advice : 언제 공통 관심 기능을 핵심 로직에 적용할 지를 정의. 예를 들어, '메서드를 호출하기 전(언제)'에 '트랜잭션 시작'(공통기능) 기능을 적용한다는 것을 정의 하고있음.

Weaving : Advice를 핵심 로직 코드에 적용하는 것을 weaving이라고 한다. 즉, 공통 코드를 핵심 로직 코드에 삽입하는 것이 weaving이라고 함.

Aspect : 여러 객체에 공통으로 적용되는 기능을 Aspect라고 한다. 트랜잭션이나 보안등이 Aspect의 좋은 예임.



스프링에서의 AOP


스프링은 자체적으로 프록시 기반의 AOP를 지원하고 있다. 따라서 스프링 AOP는 메서드 호출 Joinpoint만을 지원한다. (필드 값 변경과 같은 Joinpoint를 사용하고 싶다면 AspectJ와 같이 다양한 Joinpoint를 지원하는 AOP도구를 사용해야함)


스프링은 세가지 방식으로 AOP를 구현할 수 있도록 하고 있다.

  • XML 스키마 기반의 POJO 클래스를 이용한 AOP구현
  • AspectJ에서 정의한 @Aspect 애노테이션 기반의 AOP구현
  • 스프링 API를 이용한 AOP구현
어떤 방식을 사용하더라도 내부적으로는 프록시를 이용하여 AOP가 구현되므로 메서드 호출에 대해서만 AOP를 적용할 수 있다는 것에 유의하자. (즉, AspectJ에서 정의한 @Aspect애노테이션을 사용하더라도 메서드 호출과 관련된 Pointcut만 사용 가능)

상세한 내용은 아래의 내가 참고한 블로그의 링크를 남겨 놓겠다. 그럼 이제 AOP를 적용한 코드를 보자. 나는 로그인할 때 레디스에 저장해둔 세션 토큰 값을 확인하는 용도로 AOP를 적용하였다. 토큰 값이 유효한지 확인하는 코드는 필요한 모든 endpoint에 진입하기 전에 검사하고 싶어 controller메서드가 호출되기 전에 실행시키기위해 Before Adivce를 적용하였다.

package com.finder.genie_ai.aops;

import com.finder.genie_ai.exception.UnauthorizedException;
import com.finder.genie_ai.redis_dao.SessionTokenRedisRepository;
import com.google.gson.JsonElement;
import com.google.gson.JsonParser;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
@Aspect
public class CheckSessionValid {

private SessionTokenRedisRepository sessionTokenRedisRepository;

@Autowired
public CheckSessionValid(SessionTokenRedisRepository sessionTokenRedisRepository) {
this.sessionTokenRedisRepository = sessionTokenRedisRepository;
}

@Pointcut("execution(public * com.finder.genie_ai.controller.ShopController.*(..))")
public void controllerClassMethods() {}

@Before(value = "controllerClassMethods()")
public void checkSessionValid(JoinPoint joinPoint) {
String token = (String)joinPoint.getArgs()[0]; //JoinPoint클래스를 이용하여 타켓 메서드의 파라미터를 가로챌 수 있다.
System.out.println(token);
if (!sessionTokenRedisRepository.isSessionValid(token)) {
throw new UnauthorizedException();
}
JsonElement element = new JsonParser().parse(sessionTokenRedisRepository.findSessionToken(token));
System.out.println(element.getAsJsonObject());
}

}

Pointcut표현식에 대해서는 이글에서 자세하게 설명하지않고 따로 포스팅 하겠다. 위의 표현식을 간단히 설명하면 com.finder.genie_ai.controller.ShopController 패키지 밑에있는 모든 함수에 대하여 AOP를 적용시키겠다는 의미이다.


헤더에 토큰값을 보내면 API 진입전에 토큰 값을 검사하여야한다. 결과를 보면 토큰 값을 redis에서 잘 확인해서 데이터를 잘 뽑아 온것을 확인할 수 있다.



Redis에 저장되어있는 토큰값에 해당하는 value값도 잘 뽑아와서 로그를 뿌려주는 것도 잘된다.


이 블로그가 AOP에대한 설명이 잘되어있다. 

https://blog.outsider.ne.kr/843



반응형
Comments