본문 바로가기
Programing/Java & Spring

AOP(Aspect-Oriented Programming) 개념 및 예시

by 슈퍼와이비 2023. 9. 8.
반응형

'AOP'(Aspect-Oriented Programming)는 'OOP'(Object-Oriented Programming)의 보완적인 개념으로, 애플리케이션의 핵심 로직과는 별개로 로깅, 트랜잭션 처리, 보안 등과 같은 '부가적인 관심사' 를 분리하여 관리하는 기술입니다.

'AOP' 는 관점(Aspect) 지향 프로그래밍으로도 불리며, 핵심 로직과 부가적인 로직을 각각 모듈화하여 개발을 용이하게 합니다. 예를 들어, 로그인 기능이 필요한 웹 애플리케이션을 개발할 때, 핵심 로직은 사용자가 원하는 기능 수행이고, 로그인 기능은 부가적인 관심사입니다. AOP를 이용하면 핵심 로직과 로그인 기능을 각각 모듈화하여 개발하고, 필요한 경우 로그인 기능을 추가하거나 수정할 수 있습니다.

'AOP'를 구현하는 방법으로는 프록시 패턴, 컴파일러, 런타임 등이 있으며, 스프링 프레임워크에서는 프록시 패턴과 'AspectJ' 를 이용한 방식으로 AOP를 구현합니다. 스프링의 AOP는 메소드 호출, 예외 처리, 트랜잭션 처리 등 다양한 측면에서 활용됩니다.

 


AOP에서 사용하는 어노테이션 종류

  1. @Aspect: AOP 기능을 정의하는 클래스에 사용되는 어노테이션입니다.
  2. @Pointcut: 어떤 메소드에 대해 AOP를 적용할지를 정의하는 어노테이션입니다.
  3. @Before: 메소드 실행 전에 실행될 AOP 어드바이스를 정의하는 어노테이션입니다.
  4. @After: 메소드 실행 후에 실행될 AOP 어드바이스를 정의하는 어노테이션입니다.
  5. @AfterReturning: 메소드가 성공적으로 반환된 후 실행될 AOP 어드바이스를 정의하는 어노테이션입니다.
  6. @AfterThrowing: 예외가 발생한 후 실행될 AOP 어드바이스를 정의하는 어노테이션입니다.
  7. @Around: 메소드 실행 전/후 또는 예외 발생 시점에 실행될 AOP 어드바이스를 정의하는 어노테이션입니다.

 

 

실제 예제를 보여드리겠습니다.

 

'@Component' 어노테이션을 활용하여 스프링 컨테이너에 Bean으로 등록합니다.

'@Aspect' 어노테이션을 사용하여 AOP를 선언합니다.

'@Pointcut' 어떤 메소드에 AOP를 적용할지 선업합니다. 

@Component
@Aspect
public class LoggerAspect {
    
    @Pointcut("execution(* com.a.b..*Service.*(..))")
    public void servicePointcut(){	
    }
    
}

 

'@Before' 어노테이션을 사용하여 '@Pointcut' 메소드가 실행되기 전 로그를 찍는 메소드를 실행합니다.

@Before("servicePointcut()")
public void beforeService(JoinPoint jp){
    //String aopPrefix = "[Before Service AOP]";
    String aopPrefix = "";

    Object[] args = jp.getArgs();
    if(args != null){
        if(args.length > 0){
            String className = jp.getTarget().getClass().getName();
            String methodName = jp.getSignature().getName();
            aopPrefix += className + "." + methodName + ".";
            String logMessage; 

            int k = 1;
            for(Object arg : args){
                logMessage = aopPrefix + "Argument[" + k + "] : " + arg.toString();
                k++;
                this.logger.info(logMessage);
            }
        }
    }		
}

 

'@AfterReturning' 어노테이션을 사용하여 메소드가 성공적으로 실행된 뒤 로그를 찍도록 합니다.

@AfterReturning(pointcut = "servicePointcut()", returning = "returnData")
public void afterReturningService(JoinPoint jp, Object returnData){
    //String aopPrefix = "[AfterReturning Service AOP]";
    String aopPrefix = "";

    if(returnData != null){
        String className = jp.getTarget().getClass().getName();
        String methodName = jp.getSignature().getName();
        aopPrefix += className + "." + methodName + ".";
        String logMessage  = aopPrefix + "returnData : " + returnData.toString();
        this.logger.debug(logMessage);
    }
}

 

'@AfterThrowing' 어노테이션을 사용하여 예외가 발생했을 경우 로그를 찍도록 합니다.

@AfterThrowing(pointcut="execution(* com.a.b..*Controller.*(..))", throwing="ex")
public void exceptionControllerPointcut(JoinPoint jp, Throwable ex){
    this.logger.error("Exception[els_check_error]------------------------->" + ex.getMessage());

    for(final String e : ExceptionUtils.getRootCauseStackTrace(ex)) {
        this.logger.error(e);
    }

    String aopPrefix = "";
    Object[] args = jp.getArgs();

    if(args != null){
        if(args.length > 0){
            String className = jp.getTarget().getClass().getName();
            String methodName = jp.getSignature().getName();
            aopPrefix += className + "." + methodName + ".";
            String logMessage; 

            int k = 1;
            for(Object arg : args){
                logMessage = aopPrefix + "Argument[" + k + "] : " + arg.toString();
                k++;
                this.logger.error(logMessage);
            }
        }
    }	

}

 

반응형