기록창고

중복된 로직이 있다면 어떻게해야될까? AOP 본문

Spring

중복된 로직이 있다면 어떻게해야될까? AOP

방금시작한사람 2020. 6. 18. 15:51

개인프로젝트 중 전적을 조회할때, 중복된 로직이 존재했다.

 

게임사의 API를 통하여 데이터를 가져오기때문에 데이터를 가져오기전, 해당 ID 가 존재하는지 부터 확인을 해야한다.

 

전적 관련 API가 여러가지 존재하는데 (캐릭터, 랭크, 일반 등) 이 모든 API 호출함수에는 아이디를 체크하는 로직이 들어간다.....

 

public List<OperatorDto> getOperatorList(String id) {
	Profile profile = getProfile(id); // 아이디가 있는지 확인하는 부분 및 고유 ID 로 변환
    /* 데이터 가져오고 파싱하는 부분 */
}

public RankStatDto getRankStat(String id, ~) {
	Profile profile = getProfile(id); // 아이디가 있는지 확인하는 부분 및 고유 ID 로 변환
    /* 데이터 가져오고 파싱하는 부분 */
}

public GeneralPvpDto getGeneralPvp(String id, ~) {
	Profile profile = getProfile(id); // 아이디가 있는지 확인하는 부분 및 고유 ID 로 변환
    /* 데이터 가져오고 파싱하는 부분 */
}

 

5개 정도 API 가 있는데, 중복된 코드가 5개가 존재한다! ...  그렇다고 뺄수도없다..

 

알고 있는 선에서 생각해본건 Template Pattern 이다.

 

public abstract class R6API {
    public List<Operator> getOperatorList(id, ~) {
    	Profile profile = getProfile(id);
        return getOperator(profile.~, ~); 
    }
    
    protected abstract List<Opertator> getOperatorListUsingApi(String id, ~)
}
// R6APIConcrete

public class R6APIConcrete extends R6API {
    @Override
    protected List<Operator> getOperatorListUsingApi(String id, ~ ) {
    	// 데이터 가져오고 파싱하는 부분
    }
}

이런식으로 Profile 을 가져오는 부분을 고정해놓고 실질적으로는 아래 Concrete 가 구현 후 호출

 

근데 이 방식 또한 위에 Concrete 만 깔끔해 진것이지 실제 Template 은 5번이나 반복하긴한다.. 

 


 

그래서 질문 후 알아낸것이 AOP. 관점지향프로그래밍이다.

음.. 횡단관심사..?!

 

아래 그림을 보면 파란색하나가 어떤 로직이다. 

모든 로직에 Logging과 Security 가 존재한다. 이런걸 횡단 관심사라고 하나보다..

 

 

 

횡단관심사

 

 

 

 

내 경우는 아이디를 확인하는 작업이 횡단 관심사일것이다.

 

그래서 Spring 에서는 Aspect 라는 것을 통해 할 수 있다고 한다.. 👍👍

 

https://docs.spring.io/spring/docs/2.5.x/reference/aop.html  

여기보면 다양한 방식으로 언제 Aspect 를 실행할 것인가 에 대한 다양한 방식이 존재한다..

 

나는 그 중에서 Annotation 을 활용한 Aspect 를 사용한다.

이유는 적용되면 안되는 함수가 존재하기 때문에 필요한 부분만 Annotation을 통해 처리하기 위해서이다.

 

 

 

아주 간단한 Annotation 을 만든다. Method에만 쓸 수 있고 아무런 기능이 없고 단지 마커 역할만 한다.

@Target(ElementType.METHOD)
public @interface ConvertIdToProfileId {

}

 

Aspect

@annotation 을 통하여 ConvertIdToProfileId 가 있는 부분만 실행하겠다고 선언

 

@Around 는 언제 이 Aspect가 동작을 시작하는지에 대한 정의이다.

 5가지가 있다. 

@Before : Method 호출 이전 실행

@After   : Method 호출 이후 관계 없이 실행

@AfterReturning : Method 실행 이후 리턴 후 실행, Method 의 리턴 값을 변경할 수 있음.

@AfterThrowing : Method 실행 이후 예외가 발생하면 실행, 로깅용이나 그 후 작업을 위한 것, 강제로 리턴 불가능

@Around : Method 실행 전 후 다 관여, Return 이 없으면 아무것도 안넘김 ..

 

public class PlayerSearchAspect {
    private final PlayerService playerService;

    @Around("@annotation(org.example.springboot.service.aspect.ConvertIdToProfileId)")
    public Object convertIdToProfileIdAndProceed(ProceedingJoinPoint pjp) throws Throwable {
        Object[] args = pjp.getArgs(); // method 의 인자들을 가져옴
        String platform = args[0].toString(); 
        String id = args[1].toString(); // 변경할 부분

        Player player = playerService.findPlayerIfNotExistReturnNewEntity(platform, id);
        args[1] = player.getProfileId(); // 인자 변경
        return pjp.proceed(args); // 변경된 인자로 method 에 넣고 실행
    }
}

 

여기서 around 를 한 이유는 직접적으로 args 를 id 에서 profileid 로 변경을 한뒤,

해당 메소드를 실행해야하기 때문에 이렇게 했다. 

 

실험하다가 알게된것)

같은 annotation에 around 와 afterthrowing 을 하면 afterthrowing 작동

같은 annotation에 around 와 before 을 하면 before 작동

같은 annotation에 around 와 afterReturing 을 하면 안에 있는 다른 로직은 수행(printf 는 수행)

하지만 return 값을 강제로 null 로 했음에도 불구하고, around 의 return 값으로 나옴

 


결과

 

중복된 코드를 하나의 Aspect 로 빼서 지우고 관리도 편해졌다.  👍👍

지식이 늘어난 느낌, 내가 모르는 거지 나보다 뛰어난 사람들이 분명 만들어놨다.

 

 

'Spring' 카테고리의 다른 글

@Controller, @Service 의 차이? (@Aliasfor)  (0) 2020.07.01
@SpringBootApplication  (0) 2020.06.16
Comments