JH's Develog

[SpringCore] AOP 본문

Spring

[SpringCore] AOP

jhkimmm 2021. 12. 25. 02:20

관점 지향 프로그래밍 - Aspect Oriented Programming

OOP로 처리하기에는 다소 까다로운 부분을 AOP라는 처리 방식을 도입하여, 특정한 함수 호출 전이나 후에 함수의 로직을 건드리지 않고 손쉽게 공통적인 처리를 수행할 수 있도록 하는 기능입니다.

주로 로깅, 트랜잭션, 인증 등 에서 자주 사용되며, AOP를 과도하게 사용할 경우 코드의 분석이 다소 어려워질 수 있음을 주의 해야합니다.

위 그림처럼 A사, B사, C사에 공통적으로 나타나는 기능을 횡단 관심(cross-cutting concern)이라고 하며, 이 부분에 AOP를 적용하여 중복을 분리하고 한 곳에서 관리할 수 있습니다.

 

AOP의 기본 개념 정리

용어 의미
Aspect 횡단 관심사를 의미하며 이들을 모듈화 하는 것이 AOP이다.
Aspect모듈은 Pointcut과 Advice로 구성되어있다.
Advice AOP에서 실제로 적용하는 기능을 뜻한다.
Join Point 모듈화된 특정 기능이 실행될 수 있는 포인트이다.
Pointcut Join Point 중에서 해당 Aspect를 적용할 대상을 뽑는 조건식이다.
Target Object Advice가 적용될 대상 객체이다.
AOP Proxy 대상 객체에 Aspect를 적용하는 경우 Advice를 덧붙이기 위해 하는 작업을 AOP Proxy라고 한다.
주로 CGLIB(Code Generation Library, 런타임에 코드를 생성하는 라이브러리) 프록시를 사용하여 프록싱 처리를 한다.
Weaving Advice를 비즈니스 로직 코드에 삽입하는 것을 말한다.

Advice가 적용되는 시점을 정해주는 어노테이션은 다음과 같습니다.

  • @Before : Advice 의 Target 메서드가 호출되기 전에 실행
  • @After : Advice 의 Target 메서드가 호출된 후에 실행
  • @AfterReturning : Target 메서드가 정상적으로 실행되어 반환값을 반환한 경우 실행
  • @AfterThrowingAdvice : Target 메서드에서 Exception이 발생했을 때 실행
  • @Around : Target메서드의 전과 후에 실행

AOP 적용 예시

※ AspectJ

기본적으로 제공되는 Spring AOP로는 다양한 기법(pointcut 등)의 AOP를 사용할 수 없기 때문에, AspectJ는 AOP를 유용하게 사용하기 위해 꼭 필요한 라이브러리 입니다.

Spring boot안에 기본적으로 포함되어 있기 때문에 별도로 설치할 필요는 없습니다.

 

먼저 간단한 RestApiController를 만들어 보겠습니다.

import com.example.test1.dto.User;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api")
public class RestApiController {

    @GetMapping("/get/{id}")
    public String get(@PathVariable Long id, @RequestParam String name){
        System.out.println("get method id:"+id+"  name :"+name);
        return id+" "+name;
    }
    @PostMapping("/post")
    public User post(@RequestBody User user){
        return user;
    }

}

기본적인 get, post 요청을 받는 api를 정의했습니다. 이제 AOP클래스를 만들어보겠습니다.

@Aspect
@Component
public class ParameterAop {
    @Pointcut("execution(* com.example.test1.controller..*.*(..))")
    //controller패키지에 있는 모든 메서드에 설정
    private void cut(){}

    @Before("cut()")
    public void before(JoinPoint joinPoint){
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method method = methodSignature.getMethod();
        System.out.println(method.getName());

        Object[] args = joinPoint.getArgs(); //메서드에 들어가는 파라미터들의 배열
        for(Object obj : args){
            System.out.println("type : "+obj.getClass().getSimpleName());
            System.out.println("value : "+obj);
        }
    }

    @AfterReturning(value = "cut()", returning = "returnObj")
    // returning은 target object의 리턴 값을 받아온다는 것을 의미
    public void afterReturn(JoinPoint joinPoint, Object returnObj){
        System.out.println("return obj");
        System.out.println(returnObj);
    }
}

@Aspect 어노테이션으로 해당 클래스가 Aspect임을 알리고 @Component 어노테이션으로 IoC 컨테이너에 해당 클래스를 등록합니다.

@Pointcut("execution(* com.example.test1.controller..*.*(..))")
private void cut(){}

@Pointcut()의 파라미터로는 pointcut 표현식이 들어가며 위 표현식은 com.example.test1.controller 패키지 하위의 모든 클래스에 pointcut이 적용된다는 의미입니다.

@Before("cut()")
public void before(JoinPoint joinPoint){
    MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
    Method method = methodSignature.getMethod();
    System.out.println(method.getName());

    Object[] args = joinPoint.getArgs(); //메서드에 들어가는 파라미터들의 배열
    for(Object obj : args){
        System.out.println("type : "+obj.getClass().getSimpleName());
        System.out.println("value : "+obj);
    }
}

before이라는 Advice에 위에서 정의한 cut이라는 Pointcut이 @Before("cut()") 어노테이션을 통해 적용되었습니다.

method.getName()을 통해 해당 Advice가 적용된 메서드의 이름을 알아낼 수 있으며, jointPoint.getArgs()로는 메서드에 들어가는 파라미터들의 배열을 알아낼 수 있습니다.

 

브라우저에 "http://localhost:8080/api/get/100?name=JH" 로 api를 쏘면

AOP가 정상적으로 적용된 것을 확인할 수 있습니다.

Comments