TIL

AOP(Aspect-Oriented Programming) 파헤치기

빈코 2022. 6. 22. 20:03

객체 지향 프로그래밍(OOP)란 우리가 실생활에서 쓰는 모든 것을 객체라 하며, 객체 지향 프로그래밍은 프로그램 구현에 필요한 객체를 파악하고 각각의 객체들의 역할이 무엇인지를 정의하며 객체들 간의 상호작용을 통해 프로그램을 만드는 것을 말합니다. 하지만 OOP는 공통된 기능들이 흩어져 존재하는 단점이 있기 때문에, 이 점을 보완하고자 AOP(Aspect-Oriented Programming) 프레임워크가 탄생하게 되었습니다.

 

썸네일
Spring-AOP

 

AOP란?

AOP는 관점 지향 프로그래밍이라고 불립니다. 쉽게 말해 어떤 로직을 기준으로 핵심적인 관점과 부가적인 관점으로 나누어서 보고 그 관점을 기준으로 각각 모듈화 하겠다는 것입니다. 여기서 모듈화떤 공통된 로직이나 기능을 하나의 단위로 묶는 것을 말합니다.

 

예를 들어 흔히 볼 수 있는 게시판을 구현한다고 가정하겠습니다. 해당 유저는 로그인 화면에서 로그인을 하면 세션에 값을 저장하고, 그 세션이 있어야만 게시판을 작성하고 삭제할 수 있는 로직을 구현해야 하는 경우입니다.(비로그인 시에는 게시판을 볼 수 만 있다는 가정입니다)

 

Controller

@PostMapping("/add/freeBoard")
  public void addFreeBoard(@RequestBody FreeBoardDto freeBoardDto){
    freeBoardService.addFreeBoard(freeBoardDto);
}

 

Controller 단에서 Dto(유저가 입력한 값들)를 파라미터 값으로 넣고, FreeBoardService의 addFreeBoard 메서드를 호출하게 되면 Service 단의 코드는 아래 코드처럼 진행될 것입니다.

 

Service

@Transactional
  public void addFreeBoard(FreeBoardDto freeBoardDto){
    // Session Check
    freeBoardRepository.save(freeBoardDto);
    // History Insert
}

 

DB에 저장하기 전에 세션값을 체크한 후 게시판 DB에 저장하게 되고, 또 필요하다면 History 이력도 남겨야 합니다. 게시판을 수정할 때도 위와 같은 맥락으로 진행이 될 것입니다. 하지만, 이런 세션 체크와 이력 생성을 해야 하는 클래스가 100개가 된다면 어떻게 될까요? 이 말은 곧 100개의 클래스에 FreeBoardService와 같이 중복되는 코드를 반복해서 작성해야 함을 의미합니다.

 

만약 보안 등의 부가 기능의 정책이나 API가 변경된다면, 이를 사용하는 100개의 클래스가 모두 함께 수정돼야 합니다. 결국 서비스 클래스의 응집도가 떨어지면 가독성이 나빠지고, 변경할 부분이 명확하게 드러나지 않아서 유지보수 측면에서 어려움이 발생합니다.

 

이러한 단점을 보안하기 위해 AOP를 사용해야 합니다. 위의 코드에서 볼 수 있는 세션 체크, 이력 남기기 등 반복해서 쓰는 코드들을 흩어진 관심사(Crosscutting Concerns)라 부릅니다.

 

Aspect
Aspect 예시

 

Aspect X를 세션 처리, Aspect Y를 히스토리, Aspect Z를 보안 관련 로직이라고 가정하고 그림을 보시면, 클래스마다 분포되어 있는 Aspect들을 모듈화시켜서 핵심적인 비즈니스 로직에서 분리하여 재사용한다고 이해하시면 됩니다.

 

 

AOP 주요 개념🌠

  • Aspect : 주로 부가기능을 모듈화 한 것을 가르키며, 흩어진 관심사를 모듈화 한 것입니다. 위의 예시로 세션을 예로 들 수 있습니다.
  • Target : Aspect를 적용하는 곳입니다. 
  • Advice : 실질적으로 어떤 일을 해야 할 지에 대한 것을 나타내며, 부가기능을 담은 구현체를 가르킵니다.
  • JointPoint : Advice가 적용될 위치, 끼어들 수 있는 지점을 말합니다. 메서드 진입 지점 등 다양한 시점에 적용 가능합니다.
  • PointCut : JointPoint의 상세한 스펙을 정의하는 것입니다. 'OO메서드 진입 시점에 호출'과 같이 더욱 구체적으로 Advice가 실행될 지점을 정할 수 있습니다.

 

Spring AOP 적용하기🌟

AOP를 사용하기 위해서는 spring-starter-aop 의존성을 추가해주어야 합니다. Maven Gradle의 설정법이 다르기 때문에 혹여나 Maven과 Gradle의 차이를 모르신다면 여기를 클릭해서 확인해주세요 :)

 

Maven

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

 

Gradle

implementation 'org.springframework.boot:spring-boot-starter-aop'

 

 

AOP는 Proxy 기반🌌

 

다이어그램
Proxy Diagram

 

스프링 AOP 프레임워크는 프록시 기반(proxy-based)입니다. 그래서 어드바이스의 대상 객체마다 프록시 객체가 만들어집니다. 프록시는 AOP 프레임워크에 의해 호출하는 객체와 대상 객체 사이에 도입되는 중간 객체입니다.

 

실행 시점에서 프록시는 대상 객체 호출을 가로채고 대상 메서드에 적용할 어드바이스를 실행합니다. 스프링 AOP에서 대상 객체는 스프링 컨테이너에 등록된 빈 인스턴스입니다.

 

위의 다이어그램에서 볼 수 있듯이, 유저가 게시판을 등록할 때 addFreeBoard 메서드가 실행하기 전에 Proxy가 가로채서 Session 처리를 먼저 진행한 후에 정상적으로 게시판 등록 과정이 이루어지는 것을 알 수 있습니다.

 

하지만, 위와 같은 어드바이스의 실행 시점은 어드바이스 유형에 따라 달라집니다.

 

 

어드바이스 유형🛬

@Before 어드바이스

Before 어드바이스는 대상 메서드가 실행되기 전에 실행됩니다. Before 어드바이스가 예외를 던지지 않으면 대상 메서드가 항상 실행됩니다. AspectJ의 @Before 애너테이션을 사용하여 지정할 수 있습니다.

 

@Before(value = "execution(* binco.com.study.service.*Service.*(..))")
public void log(JoinPoint joinpoint){
	logger.info("Entering " + joinPoint.getTarget().getClass().getSimpleName())
}

 

위 코드 조각과 같이 @Before를 설정한 메서드는 첫 번째 인수로 JoinPoint를 정의할 수 있습니다. 이 JoinPoint 인수는 대상 메서드에 대한 정보를 얻기 위해 어드바이스 안에서 쓸 수 있으며, 이 인수를 사용해 대상 객체의 클래스 이름과 대상 메서드에 전달된 인수를 얻을 수 있습니다.


 

@After returning 어드바이스

after returning 어드바이스는 대상 메서드가 반환된 다음에 실행됩니다. 하지만 대상 메서드가 예외를 발생시키면 after returning 어드바이스는 실행되지 않습니다. @AfterReturning 애너테이션으로 지정할 수 있으며, 대상 메서드가 반환한 값에 접근할 수 있고, 값을 변경해 호출자에게 돌려줄 수 있습니다.

 

@Aspect
public class AspectTest {

    @PointCut(value = 
    "execution(* com.developers.dmaker.service.FreeBoardService.addFreeBoard(..))")
    private void addFreeMethod() {}
    
    @AfterReturning(value = "addFreeMethod()", returning = "aValue")
    public void afterReturningAdvice(JoinPoint joinPoint, int aValue) {
    	logger.info(
        "Value returned by" + joinPoint.getSignature().getName() +
            "method is" + aValue
        );
    }
}

@PointCut 애너테이션을 통해 사용하는 메서드를 제한하며, FreeBoardService의 addFreeBoard가 호출된 후에 afterReturningAdvice가 실행됩니다. returning 속성은 대상 메서드의 반환 값을 어드바이스에 어떤 이름으로 접근할지 지정합니다.

 

addFreeBoard의 return 값이 int형식이라면 위 예제처럼 aValue 인수의 타입을 int로 지정해야 합니다. 여기서 returning 속성을 지정하면 해당 타입을 반환하는 메서드에만 어드바이스가 적용된다는 사실을 기억해야 합니다. 만일 List 타입 객체를 반환한다면 aValue의 인수도 List로 지정해야 합니다.

 

@AfterReturning을 설정한 메서드도 JoinPoint를 첫 번째로 인수로 정의해서 대상 메서드에 대한 정보를 사용할 수 있습니다.


 

@After throwing 어드바이스

after throwing 어드바이스는 대상 메서드가 예외를 던질 때 실행되며, 던진 예외에 접근할 수 있고 @After throwing 애너테이션으로 설정합니다. 

 

@Aspect
public class AspectTest {

    @PointCut(value = 
    "execution(* com.developers.dmaker.service.FreeBoardService.*(..))")
    private void exceptionMethods() {}
    
    @AfterThrowing(value = "exceptionMethods()", throwing = "exception")
    public void afterThrowingAdvice(JoinPoint joinPoint, Throwable exception) {
    	logger.info(
        "Exception thrown by" + joinPoint.getSignature().getName() +
            "Exception type is" + exception
        );
    }
}

 

FreeBoardService의 어떤 메서드이건 간에 예외를 던지면 afterThrowingAdvice를 실행시킵니다. @AfterThrowing의 throwing 속성은 대상 메서드가 던지는 예외를 afterThrowingAdvice 메서드에서 접근할 때 쓰일 이름입니다. 

 

여기서 exception의 인수 타입이 Throwable인 것은, 대상 메서드가 어떤 타입의 예외를 던지든 afterThrowingAdvice를 실행시킨다는 것입니다. 만약, 대상 메서드가 특정 타입의 예외를 던질 때만 실행시키고 싶을 경우에는 exception 인수의 타입을 원하는 타입으로 바꿔줘야 합니다.

 

예를 들어, java.lang.IllegalStateException의 예외를 던질 때만 실행시키고 싶다면 exception 타입을 java.lang.IllegalStateException으로 설정해야 합니다.


@After 어드바이스

after 어드바이스는 대상 메서드가 정상적으로 끝났는지, 예외를 던졌는지와 관계없이 대상 메서드가 실행된 다음에 무조건적으로 실행됩니다. after 어드바이스는 @After 애너테이션으로 설정할 수 있습니다.

 

@Aspect
public class AspectTest {

    @PointCut(value = 
    "execution(* com.developers.dmaker.service.FreeBoardService.addFreeBoard(..))")
    private void addFreeMethod() {}
    
    @After(value = "addFreeMethod()")
    public void afterAdvice(JoinPoint joinPoint) {
    	logger.info(
        "After advice executed for" + joinPoint.getSignature().getName() 
        );
    }
}

 

JoinPoint의 설명은 위에서 중복적으로 했기 때문에 넘어가겠습니다


@Around 어드바이스

around 어드바이스는 대상 메서드가 실행되기 전과 후에 실행됩니다. 다른 어드바이스와 달리 ProceedingJoinPoint로 대상 메서드의 호출 여부를 결정할 수 있습니다.

 

@Aspect
public class AspectTest {
	...
    @Around(value = "execution(* com.developers..*Service.*(..))")
    public Object aroundAdvice(ProceedingJoinPoint pjp) {
    	Object obj = null;
        StopWatch watch = new StopWatch();
        watch.start();
        
        try {
        	obj = pjp.proceed();
        } catch {
        // logic
        }
        watch.stop();
        logger.info(watch.prettyPrint());
        return obj;
    }
}

 

@Around를 사용할 때, ProceedingJoinPoint가 첫 번째 인수여야 합니다. 또한 proceed 메서드를 호출하면 대상 메서드가 실행이 되고, 호출하지 않으면 대상 메서드는 실행되지 않습니다.

 

위 aroundAdvice 메서드는 해당 프로젝트의 Service 디렉터리 안에 있는 모든 메서드들이 호출될 때부터 종료될 때까지의 걸린 시간을 측정할 수 있습니다.

 

마지막으로, around 어드바이스는 반환 타입을 Object나 반환 메서드가 반환하는 타입으로 정의해야 합니다.

 


👨‍👩‍👦‍👦 오픈채팅방 운영

취업을 준비하는 예비 개발자분들을 위한 질문&답변할 수 있는 공간을 만들었습니다. 취업과 이직을 하기 위해서 어떤 걸 중점적으로 준비해야 하는지부터 포트폴리오&이력서 작성법 등 다양한 질문들을 받고 답변을 드립니다. 참여하셔서 다양한 정보 얻고 가시면 좋을 것 같네요😁

 

참여코드 : 456456

https://open.kakao.com/o/gVHZP8dg

 

비전공 개발자 취업 준비방(질문&답변)

#비전공 #개발자 #취업 #멘토링 #부트캠프 #국비지원 #백엔드 #프론트엔드 #중소기업 #중견기업 #자바 #Java #sql

open.kakao.com

 


👨‍💻 전자책 출간

아울러 제가  🌟비전공자에서 2년만에 보안 전문 중견기업으로 이직 한 방법들을 정리한 전자책을 출간 하게 되었습니다. 어떤 걸 공부해야 하는지, 이직을 위해서 무엇을 준비해야 하는지, 제가 받았던 기술 면접 리스트 등 다양한 목차로 구성되어 있습니다. 또한, 구매 시 1:1 채팅을 이용하여 포트폴리오 첨삭을 도와드리고 있습니다. 🐕전자책으로 얻은 모든 수익은 유기견 센터 '팅*벨 입양센터'에 후원될 예정입니다. 관심 있으신 분들은 아래 링크를 참고해주세요😁

https://kmong.com/gig/480954

 

비전공개발자 2년만에 중견기업 들어간 방법 | 14000원부터 시작 가능한 총 평점 0점의 전자책, 취

0개 총 작업 개수 완료한 총 평점 0점인 Binco의 전자책, 취업·이직 전자책 서비스를 0개의 리뷰와 함께 확인해 보세요. 전자책, 취업·이직 전자책 제공 등 14000원부터 시작 가능한 서비스

kmong.com


 


마치며

지금까지 AOP 프레임워크에 대해 알아보았습니다. 결론적으로 AOP는 Aspect로 모듈화하고 핵심적인 비즈니스 로직에서 분리하여 재사용하겠다는 취지라고 말할 수 있겠네요! 생각보다 용어도 어렵고 사용법이 복잡하지만, 이번 기회에 공부해서 실무에 적용해보면 좋을 것 같네요. 다음 포스팅에서 뵐게요🖐

 

관련 포스팅

* [ 객체 지향 프로그래밍(OOP)이란? ]

* [ Maven VS Gradle ]

 

Reference

* [ 스프링 AOP 총정리 ]

* [ AOP 정리 ]

* [ 배워서 바로 쓰는 스프링 프레임워크  ]

 

반응형

'TIL' 카테고리의 다른 글

PostgreSQL JSON 데이터 활용 II  (7) 2023.06.01
PostgreSQL JSON 데이터 활용  (0) 2023.05.30
TDD 정의 및 암호 검사기 예시  (0) 2022.05.25
Java MVC 패턴 바로 알기  (1) 2022.05.20
Java Enum 클래스  (0) 2022.05.19