말하는 컴공감자의 텃밭

Spring 포인트 컷? - AOP 본문

백엔드/Spring

Spring 포인트 컷? - AOP

현콩 2024. 6. 24. 16:34
728x90

스프링 프레임워크에서 포인트컷(Pointcut)은 매우 중요한 개념 중 하나인데 난 몰랐다. 예에

타점에 정확하게 ㅋㅋ 컷.. 머쑥

나는 처음 들어봤다 껄껄 

먼저 포인트컷이 뭔지 왜 쓰는지. 이해를 하고, 사용법을 살펴보자

포인트컷이 뭔데?

포인트컷은 특정 메서드 호출 또는 특정 시점 실행 지점지정하는 규칙이라고 말할 수 있다.

포인트 컷을 사용하면 애플리케이션의 특정 부분에 부가 기능을 적용할 수 있다.
포인트컷은 주로 정규 표현식을 사용하여 메서드 이름, 파라미터, 반환 타입 등을 지정할 수 있다.

그럼 왜 사용하지?

포인트컷을 사용하는 이유

 

1. 포인트컷을 통해 핵심 기능과 부가 기능을 분리할 수 있다.

 

우리는 스프링으로 개발할때 AOP가 중요하다. 업데이트 할때마다 고쳐야 할 부분이 여러곳이라면 얼마나 번거로워요...

AOP는 관점 지향 프로그래밍으로, 특정 로직을 수행하는데 있어서 핵심적인 관점부가적인 관점으로 나누어서 각각 모듈화 하는것이 특징인데. 이 포인트컷이 일조를 해준다.

 

핵심 기능이라 함은 애플리케이션의 주요 비즈니스 로직을 나타내고,
부가 기능은 로깅, 보안, 트랜잭션 관리 등과 같은 횡단 관심사(cross-cutting concerns)이다.

포인트컷은 이 두 부분을 명확하게 분리해서 가독성과 유지보수성을 높혀주게 된다.

 

2. 부가 기능을 여러 코드 영역에 재사용할 수 있어 생산성이 향상된다.

 

위와 같은 맥락이다.

포인트 컷으로 정의되어 있다면 여러 영역에서도 독립적으로 잘~ 수행될것이므로 여러 코드 영역에서 재사용할 수 있다.

코드 중복이 줄어들 것이며 생산성도 좋아진다.

예시로 반복적으로 사용할 로깅의 경우 포인트컷을 통해 사용하면 쉽게 적용할 수 있다.


3. 동적인 부가 기능 적용이 가능해 유연성이 높다.

 

포인트컷 표현식은 런타임에 동적이므로 애플리케이션 실행 환경에 따라 부가기능을 유연하게 적용시킬 수 있다.

동적으로 애플리케이션이 구성될 수 있게 해주며 유지보수성이 증가하게 된다.

예시로 특정 조건에서 다른 로그를 띄우고 싶다면 포인트컷으로 로깅 기능을 선택적으로 적용할 수 있다.


4. 핵심 로직과 부가 기능 간의 결합도가 낮아 유지보수성이 향상된다.

 

1,2,3 번의 종합이라고 할 수 있다.

메인 비즈니스 로직은 부가 기능에게 의존받지 않는 상태이므로 유연하고 확장성이 용이해진다.

기능을 추가하거나 변경할때 핵심 기능의 코드의 수정을 덜 해질것이다.


대충.. 뭔지는 알겠는데 와닿지는 않아서 비유를 해봅니다.

이해가 잘 안될때는 비유법을 참 좋아합니다 껄껄

오늘도 치킨을 뜯기 위해서  저는 집에서 배그를 켜봅니다.
열심히하고 있는데 엄마가 갑자기 와서 청소하라고 합니다.

그럼 저는 열심히 총을 쏘다가 중간에 청소를 해야 하는 상황이 와버리죠.

 

"아 지금 집중 잘되고 있었는데" 하며 흐름이 끊기는 문제가 발생합니다.

 

아 엄마~


우리는 기능 개발을 하면서도 이런 문제가 발생합니다.

우리가 어떤 일을 하고 있는데 갑자기 다른 일을 추가로 해야 하는 상황. 불편하잖아요 ~

이렇게 주 업무 외에 추가로 해야 하는 일을 "부가 기능"이라고 할 수 있습니다.

그런데 만약에 엄마가 처음부터 "게임하기 전에 청소 도와줘"라고 일이 정해졌다면?

저는 게임 하기 전에 청소를 미리 해놓을 수 있을겁니다.

이렇게 주 업무부가 업무분리해놓으면 훨씬 편한 상황이 만들어 집니다.

이 분리하는 과정을 담당하는게  AOP에서 포인트컷의 역할입니다. 포인트컷은 부가 기능을 적용할 시점을 미리 지정함으로써 개발자는 주 기능 코드에만 집중할 수 있고, 부가 기능은 자동으로 적용될 수 있도록.

 

 


그럼 어떻게 사용하나요?

 

포인트컷을 사용하는 방법은 크게 두 가지가 있다.

하나는 @Pointcut 어노테이션을 이용하는 거고, 다른 하나는 포인트컷 표현식을 직접 사용하는 거다.

 

먼저 @Pointcut 어노테이션을 이용하는 방법부터 살펴보자. 이 방법은 포인트컷 메서드를 별도로 정의하는 것!

 

@Aspect
public class LoggingAspect {
    // 포인트컷 메서드 정의
    @Pointcut("execution(public * com.example.service.*.*(..))")
    public void allServiceMethods() {}

    // 포인트컷 메서드를 참조하는 어드바이스
    @Before("allServiceMethods()")
    public void logBeforeServiceMethod(JoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().getName();
        System.out.println("Calling method: " + methodName);
    }
}

여기서 allServiceMethods() 메서드가 포인트컷 메서드다.

 

 

@Aspect

어노테이션은 이 클래스가 AOP의 Aspect 역할임을 나타내고,


@Pointcut

어노테이션으로 정의한 allServiceMethods() 메서드가 포인트컷 메서드이다.
이 메서드는 execution(public * com.example.service.*.*(..))라는 포인트컷 표현식을 사용해서 com.example.service 패키지의 모든 public 메서드를 대상으로 동작함을 선언한다.


@Before 어노테이션을 사용한 logBeforeServiceMethod() 메서드는 allServiceMethods() 포인트컷이 적용되는 지점, 즉 com.example.service 패키지의 public 메서드 호출 전에 실행되게 된다.
결국 이 메서드에서는 JoinPoint 객체를 사용하여 호출된 메서드의 이름을 출력한다.

 

결국 LoggingAspect  클래스는 com.example.service 패키지에서 모든 public 메서드 호출전에 로깅기능을 수행하게 해주게 된다.

 

 

 

두 번째 방법은 포인트컷 표현식을 직접 사용하는 것.

이 경우에는 별도의 포인트컷 메서드를 정의하지 않고 어드바이스에서 바로 표현식을 사용한다.

@Aspect
public class LoggingAspect {
    @Before("execution(public * com.example.service.*.*(..))")
    public void logBeforeServiceMethod(JoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().getName();
        System.out.println("Calling method: " + methodName);
    }
}

이 경우에는 @Before 어노테이션에 바로 포인트컷 표현식을 작성했다.

이렇게 하면 별도의 포인트컷 메서드를 정의할 필요가 없어진다.

두 가지 방법 모두 포인트컷의 정의와 사용이 가능하다. 

어떤 방식을 선택할지는 프로젝트의 요구사항이나 개발자의 선호도에 따라 다르겠지만

 


 

근데 처음보는게 많다. 뭔가 ~ 흐름은 알겠는데 저게 뭔가 하는걸 정리해보려한다.

 

1. Aspect(애스팩트)

Aspect는 AOP에서 핵심 단위로, 부가 기능을 캡슐화한 모듈이다.
로깅, 보안, 트랜잭션 관리 등의 횡단 관심사(cross-cutting concerns)를 Aspect로 정의할 수 있다.
Aspect는 포인트컷과 어드바이스로 구성된다.

 

2. Advice(어드바이스)

Advice는 Aspect 내에서 특정 포인트컷에 적용되는 부가 기능이다.
@Before, @After, @Around, @AfterReturning, @AfterThrowing 등의 어노테이션을 사용해 어드바이스를 정의한다
각 어드바이스 어노테이션은 포인트컷이 발생하는 시점을 나타낸다..

 

3.어노테이션 종류

@Pointcut: 포인트컷 메서드를 정의할 때 사용

@Aspect
public class ExampleAspect {
    @Pointcut("execution(public * com.example.service.*.*(..))")
    public void allServiceMethods() {}
}


@Before: 포인트컷 실행 전에 어드바이스를 적용.

@Aspect
public class ExampleAspect {
    @Before("allServiceMethods()")
    public void logBeforeServiceMethod(JoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().getName();
        System.out.println("메서드를 호출합니다: " + methodName);
    }
}

 


@After: 포인트컷 실행 후(정상/예외 상관없이)에 어드바이스를 적용.

@Aspect
public class ExampleAspect {
    @After("allServiceMethods()")
    public void logAfterServiceMethod(JoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().getName();
        System.out.println("메서드 종료: " + methodName);
    }
}


@AfterReturning: 포인트컷 실행 후 정상 반환 시에만 어드바이스를 적용

@Aspect
public class ExampleAspect {
    @AfterReturning("allServiceMethods()")
    public void logAfterReturningServiceMethod(JoinPoint joinPoint, Object returnValue) {
        String methodName = joinPoint.getSignature().getName();
        System.out.println("메서드: " + methodName + " 결과: " + returnValue);
    }
}


@AfterThrowing: 포인트컷 실행 중 예외 발생 시에만 어드바이스를 적용.

@Aspect
public class ExampleAspect {
    @AfterThrowing(pointcut = "allServiceMethods()", throwing = "ex")
    public void logAfterThrowingServiceMethod(JoinPoint joinPoint, Exception ex) {
        String methodName = joinPoint.getSignature().getName();
        System.out.println("메서드: " + methodName + " exception: " + ex.getMessage());
    }
}


@Around: 포인트컷 실행 전후에 어드바이스를 적용 (proceed() 메서드 호출을 통해 실제 포인트컷 실행)

@Aspect
public class ExampleAspect {
    @Around("allServiceMethods()")
    public Object logAroundServiceMethod(ProceedingJoinPoint joinPoint) throws Throwable {
        // 포인트컷 메서드 실행 전 기능 수행
        System.out.println("포인트 컷 실행 전: " + joinPoint.getSignature().getName());

        // 포인트컷 메서드 실행 - 실질적으로 메서드가 실행되는 부분
        Object result = joinPoint.proceed();

        // 포인트컷 메서드 실행 후 기능 수행
        System.out.println("포인트 컷 실행 후: " + joinPoint.getSignature().getName());

        return result;
    }
}

 

포인트컷 표현식을 구성하는 지시자

 

execution, within, this, target, args 5가지를 알아보자.
각 지시자는 포인트컷의 대상을 세부적으로 지정해준다.

 

execution

// com.example.service 패키지의 모든 public 메서드를 대상으로 함
@Pointcut("execution(public * com.example.service.*.*(..))")
public void allServiceMethods() {}

 

메서드 실행 시점을 지정하는 가장 일반적인 포인트컷 지시자.

메서드 시그니처(반환타입, 패키지/클래스명, 메서드명, 파라미터)를 기준으로 대상을 선별한다.

예제는 서비스 패키지의 모든 클래스에 속한 메서드를 대상으로 했다.

 

within 

// com.example.service 패키지의 모든 클래스 대상
@Pointcut("within(com.example.service.*)")
public void allServiceClasses() {}

 

특정 타입(클래스/인터페이스)에 속한 메서드를 대상으로 한다.

위 예제와 동일하게 서비스 패키지의 모든 클래스에 속한 메서드를 대상으로 했다.

 

this

// com.example.aspect.ExampleAspect 타입의 Bean이 관여하는 메서드 대상
@Pointcut("this(com.example.aspect.ExampleAspect)")
public void aspectMethods() {}

 

조인포인트에서 실행되는 Bean이 특정 타입(클래스/인터페이스)의 인스턴스인 경우 선별
위 예제에서는  com.example.aspect.ExampleAspect 타입의 Bean이 관여하는 메서드를 대상으로 했다.

 

target

// com.example.service.ServiceImpl 타입의 타깃 객체가 관여하는 메서드 대상
@Pointcut("target(com.example.service.ServiceImpl)")
public void serviceImplMethods() {}

 

조인포인트에서 실행되는 타깃 객체가 특정 타입(클래스/인터페이스)의 인스턴스인 경우 선별

위 예제에서는 com.example.service.ServiceImpl 타입의 타깃 객체가 관여하는 메서드를 대상으로 했다.

 

args

// 두 개의 파라미터를 가지며, 첫 번째 파라미터가 String 타입인 메서드 대상
@Pointcut("args(java.lang.String, .*)")
public void methodWithStringParam() {}

 

조인포인트에서 실행되는 메서드의 파라미터 타입을 기준으로 선별
위 예제에서는 두 개의 파라미터를 가지며, 첫 번째 파라미터가 String 타입인 메서드를 대상으로 함

 

위 지시자들로 정교하게 포인트컷을 설정할 수 있다.

728x90

'백엔드 > Spring' 카테고리의 다른 글

Spring - 로깅? 요청 매핑?  (0) 2024.04.17
Spring과 웹 애플리케이션  (1) 2023.10.29
Comments