endsharp / study

1 stars 0 forks source link

AOP #13

Open otakijae opened 1 year ago

otakijae commented 1 year ago

책 정리 & 피드백/논의를 위해

otakijae commented 1 year ago

스프링 프레임워크 단에서 proxy 객체를 통해 구현해주는 AOP가 없다면, 이런 느낌

otakijae commented 1 year ago

Aspect Oriented Programming

용어

otakijae commented 1 year ago

@Aspect, @Pointcut, @Around를 이용한 AOP 구현

Spring Proxy

AOP Aspect Advice 클래스(작업)를 위 annotation을 통해서 구현을 하게 되면, 내부적으로 proxy 객체를 생성해서 핵심 기능 수행 전후로 공통 기능을 수행할 수 있도록 만들어준다 IMG_2595

otakijae commented 1 year ago

프록시 생성 방식

otakijae commented 1 year ago

execution 명시자 표현식

otakijae commented 1 year ago

Advice 적용 순서

otakijae commented 1 year ago

aop를 단순히 로깅을 위한 작업으로 활용하는 것이 아니라, 프로젝트 내부 여러 컴포넌트를 꼬치처럼 끼워서 사용할 수 있도록 아키텍처를 가져갈 수 있지 않을까 생각. 하나의 요청이 각각의 컴포넌트를 통과하면서 수행되는 느낌 사용자 요청 ==> 사용자 인증 ==> request validation ==> api 메인 작업 ==> 후속 작업(로그 전송) 같은 흐름


aop의 가장 중요한 개념 중 하나, 스프링에서의 프록시 개념인듯 ==> 어떻게 지원하는지, 구현하는지 내부 동작 CGLIB vs JDK Dynamic Proxy 참고 related https://github.com/endsharp/study/issues/9#issuecomment-1364675344

cme10575 commented 1 year ago

[초보 웹 개발자를 위한 스프링5 프로그래밍 입문] 07. AOP 프로그래밍

설정법

spring-context, aspectjweaver 모듈 의존성 추가

public class ReculsiveCalculator implements Calculator {
   @Override
    public long factorial(long num) {
         if (num = 0)
            return 1;
        else
           return num * factorial(num - 1);
     }
}

코드가 있다고 할 때 이 함수의 실행시간을 알아내려면 어떻게 해야 하는가? 함수 앞뒤에 모두 시간을 출력하는 라인을 추가해야 하며 수정사항이 있을 때마다 반복해야한다. 이를 딱 한번만 수정하기 위해 프록시의 개념이 등장한다.

public class TimeReculsiveCalculator implements Calculator {
   private Calculator delegate;

   public TimeReculsiveCalculator(Calculator delegate) {
      this.delegate = delegete;
   }

   @Override
    public long factorial(long num) {
         long start = System.nanoTime();
         long result = delegate.factorial(num);
         long end = System.nanoTime();
         System.out.printf("%s.factorial(%d) 실행 시간 = %d\n", delegate.getClass().getSimpleName(), num, (end-start));
         return result;
     }
}

이로써 기존 코드 수정 없이 시간을 구할 수 있게 되었다.

엄밀히 말하면 프록시보다는 데코레이터 개념에 가깝다.

proxy: 접근 제어 관점에 초점

decorator: 기능 추가와 확장에 초점

프록시의 특징은 핵심 기능은 구현하지 않는다는 것

AOP

Aspect Oriented Programming. 핵심 기능과 공통 기능을 분리하는 것

구현 방법은 세 가지이다.

  1. 컴파일 시점에 코드에 공통 기능 삽입

  2. 클래스 로딩 시점에 바이트 코드에 공통 기능 삽입

  3. 런타임에 프록시 객체를 생성해 공통 기능 삽입

1, 2번은 spring AOP에서 지원하지 않는다. 원할 경우 AspectJ를 사용하면 가능하다.

호출 순서는 다음과 같다.

client -> AOP proxy -> 공통 기능 모듈, 실제 비즈니스 객체

스프링 AOP는 프록시를 자동으로 만들어주므로 직접 구성할 필요 없이, 공통 기능 클래스만 구현하면 된다.

스프링 AOP 구현

ex)

@Aspect 
public class ExeTimeAspect {
   @Pointcut("execution(public * chap07..*(..))")  // chap07와 하위 패키지 public method에 적용
    private void publicTarget() {}
    @Around("publicTarget()")  // 메서드의 시작과 끝에 적용
    public Object measure(ProceedingJointPoint joinPoint) throws throwable {   // ProceedingJointPoint: 대상 메서드 호출 시 사용
        ...
        joinpoint.proceed(); // 대상 메서드 호출
        ...
    }
}

프록시 생성 방식

빈 객체가 인터페이스를 상속하면 기본적으로 프록시는 그 인터페이스를 상속한다.

그렇기 때문에 아래 코드는 BeanNotOfRequiredTypeException 오류를 발생한다.

applicationContext.getBean("calculator", ReculsiveCalculator);

인터페이스인 applicationContext.getBean("calculator", Calculator); 로 가져와야 한다.

excution 명시자 표현식

앞서 Aspect를 적용할 위치를 지정할 때 사용한 @Pointcut 설정에서 excution 명시자를 사용했다.

패턴은 다음과 같다.

execution(수식어패턴? 리턴타입패턴 클래스이름패턴?메서드이름패턴(파라미터패턴))

수식어패턴은 생략가능하며 public, private 등이 온다. 각 패턴은 *을 사용하여 모든 값을 표현하며, 점 두개(..)를 이용하여 0개 이상을 표현한다.

// 예시
execution(public void set*(..))

Advice 적용 순서

한 클래스에 두 개 이상의 프록시(어드바이스)가 적용된다면, 그 순서는 자바나 스프링 프레임워크 버전에 따라 달라진다. 그러므로 순서가 중요하다면 @Order 애노테이션을 통해 직접 지정해줄 수 있다.

@Order(숫자)는 숫자의 값이 작은 것부터 큰 것 순으로 Advice를 적용한다.

//예시
@Order(1)
@Aspect
public class ExeTimeAspect { 
}