본문 바로가기
Everyday Study

2024.08.28(수) { Aspect 적용 시점, 메서드 매처(Method Matcher)와 클래스 필터 }

by xogns93 2024. 8. 28.

애스펙트(Aspect)를 적용해야 할 시점

 

애스펙트(Aspect)를 적용해야 할 시점은 코드에서 반복적으로 발생하는 횡단 관심사(Cross-Cutting Concerns)를 처리하고자 할 때입니다. 횡단 관심사란 여러 모듈이나 클래스에서 공통적으로 필요하지만, 핵심 비즈니스 로직과는 직접적으로 관련이 없는 기능들을 의미합니다. 이러한 관심사를 적절히 분리하고 관리하기 위해 애스펙트 지향 프로그래밍(AOP)을 사용합니다.

 

 

애스펙트를 적용해야 할 주요 시점

 

  1. 로깅(Logging)
    • 상황: 애플리케이션의 여러 부분에서 로그를 기록하는 코드가 필요할 때.
    • 적용 이유: 로깅 코드를 각 클래스에 반복해서 작성하지 않고, 애스펙트로 분리하여 관리할 수 있습니다. 이는 코드의 중복을 줄이고, 로깅 로직을 중앙에서 관리할 수 있게 합니다.
    • 예시: 메서드가 호출될 때마다 로그를 기록하거나, 예외가 발생했을 때 로그를 남기는 경우.
    @Aspect
    public class LoggingAspect {
        @Before("execution(* com.example.service.*.*(..))")
        public void logBeforeMethod(JoinPoint joinPoint) {
            System.out.println("Method called: " + joinPoint.getSignature());
        }
    }
  2. 보안(Security)
    • 상황: 사용자 인증 또는 권한 검사가 필요한 경우.
    • 적용 이유: 보안 관련 로직을 애스펙트로 분리하면, 각 서비스나 컨트롤러 클래스에서 일일이 보안 검사를 수행하는 대신, 애스펙트에서 이를 처리할 수 있습니다.
    • 예시: 특정 메서드가 호출되기 전에 사용자의 권한을 확인하거나, 인증되지 않은 접근을 막는 경우.
    @Aspect
    public class SecurityAspect {
        @Before("execution(* com.example.service.SecureService.*(..))")
        public void checkUserAuthentication() {
            // 인증 로직
            if (!isUserAuthenticated()) {
                throw new SecurityException("User not authenticated");
            }
        }
    }
  3. 트랜잭션 관리(Transaction Management)
    • 상황: 데이터베이스 트랜잭션을 처리할 때.
    • 적용 이유: 트랜잭션 시작, 커밋, 롤백 등의 로직을 애스펙트로 분리하여 관리하면, 트랜잭션 관리 코드를 각 서비스 메서드에 직접 작성할 필요가 없습니다.                                                                                                   
    • 예시: 데이터베이스 작업이 시작될 때 트랜잭션을 시작하고, 성공 시 커밋, 실패 시 롤백하는 경우.
    @Aspect
    @Component
    @Transactional
    public class TransactionAspect {
        @Around("execution(* com.example.service.*.*(..))")
        public Object manageTransaction(ProceedingJoinPoint joinPoint) throws Throwable {
            // 트랜잭션 시작
            Object result = null;
            try {
                result = joinPoint.proceed(); // 메서드 실행
                // 트랜잭션 커밋
            } catch (Exception e) {
                // 트랜잭션 롤백
                throw e;
            }
            return result;
        }
    }
  4. 캐싱(Caching)
    • 상황: 동일한 요청에 대해 반복적인 작업을 줄이기 위해 결과를 캐시할 때.
    • 적용 이유: 캐싱 로직을 애스펙트로 분리하면, 각 서비스 메서드에 캐시 관련 코드를 넣지 않고도 캐싱 기능을 추가할 수 있습니다.
    • 예시: 데이터베이스에서 동일한 데이터를 여러 번 조회할 때, 이전에 조회한 결과를 캐시하여 성능을 개선하는 경우.
    @Aspect
    public class CachingAspect {
        private Map<String, Object> cache = new HashMap<>();
    
        @Around("execution(* com.example.service.*.*(..))")
        public Object cacheResult(ProceedingJoinPoint joinPoint) throws Throwable {
            String key = joinPoint.getSignature().toString();
            if (cache.containsKey(key)) {
                return cache.get(key);
            }
            Object result = joinPoint.proceed();
            cache.put(key, result);
            return result;
        }
    }
  5. 예외 처리(Exception Handling)
    • 상황: 특정 유형의 예외를 전역적으로 처리하거나 예외 발생 시 로그를 기록해야 할 때.
    • 적용 이유: 예외 처리 로직을 애스펙트로 분리하면, 각 메서드에서 반복적인 예외 처리 코드를 작성할 필요가 없으며, 공통된 예외 처리 정책을 중앙에서 관리할 수 있습니다.
    • 예시: 특정 예외가 발생했을 때 사용자에게 동일한 메시지를 반환하거나, 예외 발생 시 로그를 남기는 경우.
    @Aspect
    public class ExceptionHandlingAspect {
        @AfterThrowing(pointcut = "execution(* com.example.service.*.*(..))", throwing = "ex")
        public void handleException(JoinPoint joinPoint, Throwable ex) {
            System.out.println("Exception in method: " + joinPoint.getSignature());
            System.out.println("Exception message: " + ex.getMessage());
        }
    }

결론

애스펙트는 코드의 횡단 관심사를 모듈화하여 관리할 때 유용합니다. 이러한 횡단 관심사는 여러 곳에서 공통적으로 필요하지만, 비즈니스 로직과는 독립적인 기능들입니다. 애스펙트를 사용하면 코드의 중복을 줄이고, 유지보수성을 높일 수 있습니다. 위의 예시는 애스펙트를 적용해야 할 몇 가지 대표적인 상황을 보여줍니다.

따라서 애스펙트를 적용할 시점은 반복적으로 사용되거나 공통된 관심사가 여러 모듈에 걸쳐 발생할 때입니다. 이를 통해 애플리케이션의 설계를 개선하고, 코드의 복잡성을 줄일 수 있습니다.

 


모듈화

모듈화(Modularity)는 소프트웨어 설계에서 매우 중요한 개념으로, 시스템을 독립적이고 재사용 가능한 단위(모듈)로 분리하는 것을 의미합니다. 이러한 모듈은 특정 기능이나 역할을 수행하며, 다른 모듈과 결합되어 전체 시스템을 구성합니다. 모듈화를 통해 코드를 구조화하고 관리하기 쉽게 만들며, 유지보수성을 크게 향상시킬 수 있습니다.

 

Java 패키지: Java에서 패키지는 모듈화의 기본 단위입니다. 패키지는 관련된 클래스와 인터페이스를 그룹화하여 관리하며, 패키지 간에 명확한 인터페이스를 정의함으로써 모듈화를 실현합니다.

 


 

메서드 매처(Method Matcher)와 클래스 필터(Class Filter)

 

 

메서드 매처(Method Matcher)와 클래스 필터(Class Filter)는 AOP(Aspect-Oriented Programming)에서 특정 클래스와 메서드에 애스펙트를 적용할지 여부를 결정하는 데 사용되는 중요한 개념입니다. 이 두 개념은 각각 클래스와 메서드 수준에서 필터링을 수행합니다.

 

1. 클래스 필터 (Class Filter)

 

클래스 필터(Class Filter)는 특정 클래스에 애스펙트를 적용할지 여부를 결정합니다. 즉, 클래스 필터는 어떤 클래스에 애스펙트를 적용할지(또는 적용하지 않을지)를 결정하는 역할을 합니다.

  • 용도: 클래스 필터를 사용하면 특정 클래스 전체에 대해 애스펙트를 적용할지 선택할 수 있습니다. 클래스 필터가 참이면 그 클래스 내의 모든 메서드가 애스펙트의 대상이 됩니다.
  • 예시:
    • within(com.example.service..*): com.example.service 패키지 아래의 모든 클래스에 대해 애스펙트를 적용합니다.
    • target(com.example.service.MyService): MyService 클래스에 대해서만 애스펙트를 적용합니다.

 

2. 메서드 매처 (Method Matcher)

메서드 매처(Method Matcher)는 특정 클래스의 메서드 중에서 어떤 메서드에 애스펙트를 적용할지 결정합니다. 즉, 클래스 필터에 의해 선택된 클래스 내에서, 어떤 메서드가 애스펙트의 대상이 될지를 결정하는 역할을 합니다.

  • 용도: 메서드 매처를 사용하면 클래스 내에서 특정 메서드에만 애스펙트를 적용할 수 있습니다. 메서드 매처는 메서드의 이름, 반환 타입, 매개변수 타입 등을 기준으로 메서드를 필터링합니다.
  • 예시:
    • execution(* get*(..)): 메서드 이름이 get으로 시작하는 모든 메서드에 애스펙트를 적용합니다.
    • execution(* *(String, ..)): 첫 번째 매개변수가 String 타입인 모든 메서드에 애스펙트를 적용합니다.

메서드 매처와 클래스 필터의 차이점

  • 클래스 필터는 애스펙트를 적용할 클래스를 필터링합니다. 이 필터가 적용된 클래스 내의 모든 메서드는 기본적으로 애스펙트의 적용 대상이 됩니다.
  • 메서드 매처는 클래스 필터에 의해 선택된 클래스 내의 특정 메서드를 필터링합니다. 이 필터는 클래스가 선택된 후, 어떤 메서드에 애스펙트를 적용할지를 세밀하게 결정합니다.

예제 코드로 이해하기

다음은 Spring AOP에서 클래스 필터와 메서드 매처를 결합하여 사용하는 간단한 예제입니다:

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class LoggingAspect {

    // 클래스 필터: com.example.service 패키지 내의 모든 클래스
    // 메서드 매처: 메서드 이름이 "get"으로 시작하는 메서드
    @Before("within(com.example.service..*) && execution(* get*(..))")
    public void logBeforeGetMethods() {
        System.out.println("Logging before a 'get' method execution");
    }
}

 

결론

  • **클래스 필터(Class Filter)**는 애스펙트를 적용할 클래스를 결정합니다.
  • **메서드 매처(Method Matcher)**는 선택된 클래스 내에서 특정 메서드를 타겟으로 애스펙트를 적용할지 결정합니다.