본문 바로가기
Spring

관점 지향 프로그래밍(AOP; Aspect Orient Programming)

by S2채닝S2 2023. 3. 8.

AOP

- 관점 지향 프로그래밍

- Aspect Orient Programming

- aspect: 관점, 기능, 관심.

- Cross Cutting Concern을 해결해주는 방법 중 하나.

- 핵심기능(Business Logic)과 부가기능(Cross Cutting Concerns)을 분리한다.

- Cross Cutting Concerns의 분리를 허용함으로써 모듈성을 증가시키는 것이 목적인 프로그래밍 패러다임.

- 코드 자체를 수정하지 않는 대신 기존의 코드에 추가 동작(어드바이스)을 추가함으로써 수행하며, <함수의 이름이 set으로 시작하면 모든 함수 호출을 기록한다>와 같이 어느 코드가 포인트컷 사양을 통해 수정되는지를 따로 지정한다. 기능의 코드 핵심부를 어수선하게 채우지 않고도 비즈니스 로직에 핵심적이지 않은 동작들을 프로그램에 추가할 수 있게 한다. 

 

Cross Cutting Concerns

  : 각 layer에서 공통으로 다뤄야할 문제들.

  : Presentation Layer, Business layer, Data Access layer에서 공통적으로 Logging, Transaction Manager, Security를 고민해야 한다.

  AOP는 Logging, Transaction Manager, Security 각각의 layer에서 따로 만들어 관리하는 것이 아니라 횡단으로 각각의 layer를 걸쳐서 적용하는 부가기능을 지원한다.

따라서 AOP는 핵심 기능(혹은 business logic)과 Cross Cutting Concerns(즉, 부가기능)을 분리해서 프로그램을 만들 수 있게 해주는 방식.

 

 

-  AAAA & BBBB: 부가기능 -> 분리. 부가기능을 관리하는 모듈로 분리하여 그 모듈에서 자체적으로 관리.

이체라는 method에 어떻게 부가기능을 추가할까? -> AOP가 관리

class 계좌이체서비스 {
	method 이체(){
    	AAAA
        비즈니스 로직
        BBBB
    }
    
    method 계좌확인(){
    	AAAA
        비즈니스 로직
        BBBB
    }
}

class 대출승인서비스{
	method 승인(){
    	AAAA
        비즈니스로직
        BBBB
    }
}

 

AOP 적용방법

- 컴파일 시점: AOP 프레임워크가 컴파일 전에 공통 코드를 소스코드에 삽입하는 방식. 부가기능을 소스에 삽입하는 방식

- 클래스 로딩 시점: 클래스 로딩할때 바이트코드에 부가기능을 삽입. 클래스 로딩 시점에 Base Binary에 부가기능 삽입

- 런타임 시점: Spring에서 제공하는 AOP방식. 프록시(proxy)를 만들어 프록시 객체가 처리. 비즈니스 로직 앞-뒷단의 부가기능 처리

 

AOP 적용 예제

class CalculatorImpl implements Calculator{

    @Override
    public int add(int a, int b) {
        return a+b;
    }
}
interface Calculator{
    int add(int a, int b);
}

class LoggingInvocationHandler implements InvocationHandler{

    private static final Logger log = LoggerFactory.getLogger(LoggingInvocationHandler.class);

    private final Object target;

    public LoggingInvocationHandler(Object target){
        this.target = target;
    }


    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        /*
        * CalculatorImpl 이 target이되고 target의 method가 invoke를 실행
        * CalculatorImpl의 add 메소드가 invoke를 실행한다.
        * */

        log.info("{} executed in {}", method.getName(), target.getClass().getCanonicalName());
        return method.invoke(target, args);
    }
}


public class JdkProxyTest {
    private static final Logger log = LoggerFactory.getLogger(JdkProxyTest.class);
    public static void main(String[] args) {
        var calculator = new CalculatorImpl();

        /*
        Dynamic Proxy: proxy instance 생성. invocation handler 전달해야함.
          - invocation handler가 (interface) Calculator를 다루는 proxy instance를 생성
          - proxy instance는 (interface) Calculator의 구현체 CalculatorImpl을 target으로 함.

        JDK Dynamic Proxy
          : Dynamic Dispatch
          : 동적 디스패치: 컴파일 시에는 Calculation interface type
          실행 시 실제 객체에 담긴 클래스에 따라 proxy target 이 결정됨.
          Calculation을 상속하는 구현 클래스가 CalculatorImpl1, CalculatorImpl2 두개일 경우,
          CalculatorImpl1.add()를 실행할지 CalculatorImpl2.add()를 실행할지 실행 시점에서 결정됨.

          마찬가지로 proxy target object 도 CalculatorImpl1인지 CalculatorImpl2인지 실행 시점에 결정됨
          Calculator calculator = new CalculatorImpl1();
          * target object로 인터페이스 calculator 전달
            >> 컴파일: Calculator type
            >> 실행 시점, proxy 인스턴스 생성
                -> calculator에 담긴 클래스는 CalculatorImpl1
                --> target object가 CalculatorImpl1인 proxy instance가 생성됨
        */
         Calculator proxyInstance = (Calculator) Proxy.newProxyInstance(
                LoggingInvocationHandler.class.getClassLoader(),
                new Class[]{Calculator.class},
                new LoggingInvocationHandler(calculator));

         //Proxy test
         var res = proxyInstance.add(1, 2);
         log.info("Add -> {}", res);

    }
}

 

 

Spring AOP

 

- pom.xml에 dependency 추가

<!--Spring AOP-->
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

<!--aspectj-->
<dependency>
   <groupId>org.aspectj</groupId>
   <artifactId>aspectjweaver</artifactId>
   <version>1.9.6</version>
   <scope>compile</scope>
</dependency>

 

Spring AOP 주요 용어

- 타겟(Target)

  : 핵심 기능을 담고 있는 모듈로서 부가기능을 부여할 대상(클래스)

 

- 조인포인트(Join Point)

    : 어드바이스가 적용될 수 있는 위치(부가기능을 적용할 수 있는 위치)

    : 타겟 객체가 구현한 인터페이스의 모든 메서드

 

- 포인트컷(Pointcut)

    : 어드바이스를 적용할 타켓의 메서드를 선별하는 표현식

    : 포인트컷 표현식은 execution으로 시작하고 메서드의 Signature를 비교하는 방법을 주로 이용함

    : 여러 개의 조인포인트 중에 어디에 부가기능을 적용시킬지 

 

- 애스팩트(Aspect)

    : 애스팩트 = 어드바이스 + 포인트컷

    : Spring에서는 Aspect를 빈으로 등록해서 사용

    : 부가기능 set을 aspect로 모듈화

    : @Aspect 어노테이션을 통해 aspect를 정의

 

- 어드바이스(Advice)

    : 어드바이스는 타겟의 특정 조인포인트에 제공할 부가기능

    : Advice에는 다음 그림과 같이 @Before, @After, @Around, @AfterReturning, @AfterThrowing 등이 있음

 

- 위빙(Weaving)

    : 타겟의 조인 포인트에 어드바이즈를 적용하는 과정

 

 

 

예제

 

- 어노테이션 생성(선택)

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface TrackTime {
}

- Pointcut 설정 클래스

public class CommonPointcut {
    //"execution(public * org.prgrms.kdt..*Service.*(..))"을 Pointcut method로 뺌.
    @Pointcut("execution(public * org.my.app..*Service.*(..))")
    public void servicePublicMethodPointcut(){}

    @Pointcut("execution(* org.my.app..*Repository.*(..))")
    public void repositoryMethodPointcut(){}

    @Pointcut("execution(* org.my.app.kdt..*Repository.insert(..))")
    public void repositoryInsertPointcut(){}
}

- Aspect 클래스

  * @Around(execution(public * org.my.app..*Repository.insert(..)))

        : org.my.app 패키지 아래 이름이 Repository로 끝나는 클래스의 insert 메소드에 적용

  * @Around("org.my.app.aop.CommonPointcut.repositoryInsertPointcut()")

        : org.my.app.aop.CommonPointcut 클래스에서 정의한 포인트컷 repositoryInsertPointcut()을 적용

  * @Around("@annotation(org.my.app.aop.TrackTime)")

        : @TrackTime이 붙은 메소드에 적용

@Aspect
@Component
public class LoggingAspect {

    private static final Logger log = LoggerFactory.getLogger(LoggingAspect.class);
	
    /*
    @Around(포인트컷 표현식)
    - 포인트컷 지정자: 어드바이저를 어떻게 적용시킬지 aop에게 알려줌
   -  execution(): 메소드 실행 시점에 어떻게 하겠다. 포인트컷 패턴을 어떻게 적용시키겠다. 가장 기본적
    - execution(접근지정자 리턴타입 클래스)
    >> execution(public * org.my.app..*Repository)
          - public 메소드
          - *: 모든 리턴타입
          - org.my.app..*Repository: org.my.app 패키지 안의 모든 패키지, Repository로 끝나는 클래스에
          어드바이저를 적용

    >> execution(org.my.app..*.*())
        - org.prgrms.kdt 아래 전체 패키지, 전체 클래스, 전체 메소드에 적용

    >> execution(org.my.app..*Repository.find*())
        - Repository로 끝나는 클래스의 find로 시작하는 메소드에 적용

    >> execution(org.my.app..*.*(..)): 모든 클래스 모든 메소드, 인자로 어떤 것을 받는지 상관 없음
    >> exectution(org.my.app..*.*(long, ..)): 모든 클래스의 메소드 중 첫 번째 인자를 long으로 받는 것에 적용
    */
    
//    @Around("execution(org.my.app..*.*(..))")
//    @Around("org.my.app.aop.CommonPointcut.repositoryInsertPointcut()")
    @Around("@annotation(org.my.app.aop.TrackTime)") //특정 annotation(TrackTime)이 부여된 메소드에 적용
    public Object log(ProceedingJoinPoint joinPoint) throws Throwable {
        log.info("Before method called. {}", joinPoint.getSignature().toString());
        var startTime = System.nanoTime(); // method 호출 시점
        var result = joinPoint.proceed();
        var endTime = System.nanoTime() - startTime; // method 가 호출되고 반환될 때 까지의 시간
        log.info("After method called with result => {} and time taken by {} nanoseconds", result, endTime);
        return result;
    }
}

- 테스트

@Test
@DisplayName("Aop test")
public void testOrderService() {
    var fixedAmountVoucher = new FixedAmountVoucher(UUID.randomUUID(), 100);
    voucherRepository.insert(fixedAmountVoucher);
}

 

@Repository
public class MemoryVoucherRepository{
	
    /*
    Aspect에서 @TrackTime이 붙은 메소드에만 적용하도록 설정해놓았기 때문에
    @TrackTime을 붙이지 않으면 어드바이저가 적용되지 않는다.
    */
    @TrackTime
    public Voucher insert(Voucher voucher) {
        storage.put(voucher.getVoucherId(), voucher);
        return voucher;
    }
}

'Spring' 카테고리의 다른 글

Spring MVC  (0) 2023.03.14
Spring Transaction Management  (0) 2023.03.09
RuntimeException 1073741515: Embedded Mysql  (0) 2023.03.02
Spring JDBC: DBCP  (0) 2023.02.27
Spring Test: Junit  (0) 2023.02.17

최근댓글

최근글

skin by © 2024 ttuttak