Lang/JAVA

[AOP] 관점지향프로그래밍에 대해

quantumee 2024. 2. 27. 15:25

AOP

(Aspect Oriented Programming)

 

1. 개요

 

AOP는 OOP(객체지향프로그래밍)의 대체제가 아니라 보완하기 위한 개념이다.

 

클래스 단위로 나누기 애매한 로직을 처리하는 데 주로 사용된다.

 

그러한 로직을 횡단 관심사(cross-cutting concerns)라 한다. 

 

횡단 관심사의 대표적 예는 로깅, 보안,트랜잭션 등이 있다. 

 


 

2. 경험

예전에 패스워드를 bcrypt로 암호화를 진행했었는데 이 때문에 복호화 작업이 필요했었다. 

그것을 예시로 보자면

 

처음 ) 자주 쓸거라 생각못해서 필요할 때마다 로직 복붙했다. ( 객체지향X, 관점지향X )

 

도중 ) 이 로직이 계속 쓸일이 생기자 class로 따로 생성해서 메소드로 관리했다. (객체지향O, 관점지향X )

ㄴ> 문제점: 클래스화 하면서도 주요 기능이 아니라 조금 애매하다란 생각이 들었다. 

ㄴ>( 그 클래스에 그 메소드만 덜렁있는데 다른 메소드랑 묶기도 애매하고 그렇다고 그냥 두기에는 클래스가 응근 리소스 소모가 있을텐데 아깝기도 해서 애매하다란 생각이 들었던거 같다 )

 

AOP를 적용하면? ) Aspect 에서 로직을 넣고 적용 대상자와 실행 타이밍을 적용한다. ( 객체지향O, 관점지향X )

 ㄴ> Aspect에서 적용대상을 교체하면 되기에 비즈니스 로직은 건드리지 않아도 수정이 가능하다.

 ㄴ> 비즈니스 로직에 부가적인 코드가 없어 가독성이 올라간다.

 ㄴ> 비슷하게 부가적인 예외처리, 로깅 , 트랜잭션 관리 등의 코드를 쉽게 관리할 수 있다.

 



용어정리

1. Aspect

: 위에서 설명한 흩어진 관심사를 모듈화 한 것. 주로 부가기능을 모듈화함

 

2. PointCut

:JointPoint의 상세한 스펙을 정의한 것. 'A란 메서드의 진입 시점에 호출할 것'과 같이 더욱 구체적으로 Advice가 실행될 지점을 정할 수 있음 ( 필수는 아님, 하지만 가독성을 위해 사용 -> 사용하지 않을시 해당 내용을 advice에 바로 입력 )

 

3. Target

: Aspect를 적용하는 곳 (클래스, 메서드 .. 등 -> 중심 비즈니스 로직 )

 

4.JoinPoint

:Advice가 적용될 위치, 끼어들 수 있는 지점. 메서드 진입 지점, 생성자 호출 시점, 필드에서 값을 꺼내올 때 등 다양한 시점에 적용가능 ( 특정한 anotation 등이 아닌 추상적 개념 )

 

5. Advice

: 실질적으로 어떤 일을 해야할 지에 대한 것, 실질적인 부가기능을 담은 구현체 (Aspect 안에 위치)


 


1. Aspect

< 예시 코드 >

@Aspect
@Component
public class LoggingAspect {

    @Pointcut("execution(* com.example.service.*.*(..))")
    private void serviceLayer() {
        // service layer pointcut
    }

    @Pointcut("execution(* com.example.controller.*.*(..))")
    private void controllerLayer() {
        // controller layer pointcut
    }

    @Before("serviceLayer()")
    public void logServiceAccess(JoinPoint joinPoint) {
        // logging for service layer
    }

    @Before("controllerLayer()")
    public void logControllerAccess(JoinPoint joinPoint) {
        // logging for controller layer
    }
}

 

=> 클래스에 @Aspect 라는 어노테이션을 써서 Aspect라는 것을 선언한다.

 


2. PointCut

1) execution : 메서드 실행을 의미

@Pointcut("execution(* com.example..*.*(..))") //com.example 패키지 이하의 모든 메서드를 선택
public void allMethods() {}


2) within : 특정 타입 내의 메서드를 의미

@Pointcut("within(com.example.MyClass)") // com.example.MyClass 타입의 모든 메서드를 선택
public void allMethodsInMyClass() {}


3) this : 빈 객체의 타입을 의미

@Pointcut("this(com.example.MyInterface)") // com.example.MyInterface 인터페이스를 구현하는 빈의 모든 메서드를 선택
public void allMethodsInBeansImplementingMyInterface() {}


4) target : 프록시 대상 객체의 타입을 의미

@Pointcut("target(com.example.MyClass)") // com.example.MyClass 타입의 객체를 대상으로 하는 프록시의 모든 메서드를 선택
public void allMethodsInProxiesTargetingMyClass() {}


5) args : 메서드의 인자를 의미합니다.

@Pointcut("args(java.lang.String)") // String 인자를 받는 모든 메서드를 선택
public void allMethodsWithStringArgument() {}


6) @target : 대상 객체의 어노테이션을 의미

@Pointcut("@target(com.example.MyAnnotation)") // @MyAnnotation 어노테이션이 붙은 대상 객체의 모든 메서드를 선택
public void allMethodsInAnnotatedBeans() {}


7) @args : 메서드 인자의 어노테이션을 의미

@Pointcut("@args(com.example.MyAnnotation)") // @MyAnnotation 어노테이션이 붙은 인자를 받는 모든 메서드를 선택
public void allMethodsWithAnnotatedArguments() {}


8) @within : 빈 객체의 어노테이션을 의미

@Pointcut("@within(com.example.MyAnnotation)") // @MyAnnotation 어노테이션이 붙은 빈의 모든 메서드를 선택
public void allMethodsInAnnotatedBeans2() {}


9) @annotation : 메서드의 어노테이션을 의미

@Pointcut("@annotation(com.example.MyAnnotation)") // @MyAnnotation 어노테이션이 붙은 모든 메서드를 선택
public void allAnnotatedMethods() {}

 


3. Target 

: 위의 Pointcut을 통해 가리키는 메서드에 해당한다.

 


4. JoinPoint

: PointCut에서 지정한 대상인 Target에게 Advice를 적용할 시점 혹은 지점을 말한다.

 


5. Advice

1. @Before: 메서드 실행 전에 동작을 수행

@Before("execution(* com.example..*.*(..))")
public void beforeAdvice() {
    System.out.println("Before method execution");
}

 


2. @After:
메서드 실행 후에 동작을 수행 (예외 발생 여부와 상관없이).

@After("execution(* com.example..*.*(..))")
public void afterAdvice() {
    System.out.println("After method execution");
}



3. @AfterReturning:
메서드가 성공적으로 실행된 후에 동작을 수행

@AfterReturning(pointcut = "execution(* com.example..*.*(..))", returning = "result")
public void afterReturningAdvice(Object result) {
    System.out.println("Method returned value is : " + result);
}

 


4. @AfterThrowing:
메서드에서 예외가 발생했을 때 동작을 수행

@AfterThrowing(pointcut = "execution(* com.example..*.*(..))", throwing = "error")
public void afterThrowingAdvice(IllegalArgumentException error) {
    System.out.println("An error has occurred: " + error.getMessage());
}

 


5. @Around:
메서드 실행 전후에 동작을 수행

@Around("execution(* com.example..*.*(..))")
public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
    System.out.println("Before method execution");
    Object result = joinPoint.proceed();  // continue on the intercepted method
    System.out.println("After method execution");
    return result;
}

 

 



AOP 활용 상황

1. 로깅  =>  @After or @Before 

2. 트랜잭션관리 => @Around ( but, 보통은 AOP가 아닌 @Transactional로 처리 )

3. 보안  => @Before   but, 보통은 AOP가 아닌 @Cacheable 로 처리 ) @Cacheable

4. 오류처리 => @AfterThrowing

5. 캐싱  => @Around

6. 이벤트 발행 => @AfterReturning

7. 성능 확인 => @Around