ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [CS] AOP
    CS 2023. 1. 4. 10:51
    728x90

    AOP

    Aop는 관점 지향 프로그래밍이라고 불린다.

    관점 지향은 어떤 로직을 기준으로 핵심적인 관점, 부가적인 관점으로 나누어서 보고 그 관점을 기준으로 모듈화 즉, 공통된 로직이나 기능을 하나로 모으겠다는 것이다.

     

    결론, Aop는 흩어진 관심사를 하나로 모으겠다. ( 공통 로직으로 처리하겠다! )

     

    관심사 모으기

    → AOP는 OOP를 대체하기 위한 것이 아니라 횡단 관심사를 깔끔하게 처리하기 어려운 OOP의 부족한 부분을 보조하는 목적으로 개발되었다

    Aop 관련 용어

    • Aspect : 흩어진 관심사를 모듈화 한 것.
    • Target : Aspect를 적용하는 곳. 클래스, 메서드 등..
    • Advice : 실질적인 부가기능을 담은 구현체
    • Join Point : Advice가 적용될 위치 혹은 끼어들 수 있는 시점.
      • 참고로 스프링에서 Join Point는 언제나 메서드 실행 시점을 의미 한다.
      • 메서드 진입 시점, 생성자 호줄 시점, 필드에서 꺼내올 시점 등 끼어들 시점을 의미.
    • Point Cut : Join Point의 상세한 스펙을 정의한 것.
      • 주로 AspectJ 표현식을 사용해서 지정
      • 프록시를 사용하는 스프링 AOP는 메서드 실행 지점만 포인트컷으로 선별 가능
      • 조인 포인트중 어드바이스 적용될 위치

    aspect (하나의 모듈): 부가 기능 + 부가 기능을 어디에 적용할지 선택하는 기능 어드 바이저 : 어드바이스 ( 부가기능 ) + 포인트컷 ( 적용 대상 )

     

    Aop 적용 방법

    1. 컴파일 타임 적용

    → 컴파일 시점에 바이트 코드를 조작하여 AOP가 적용된 바이트 코드를 생성하는 방법.

    → 단점 : 부가 기능을 적용하려면 특별한 컴파일러도 필요하고 복잡하다.

    1. 로드 타임 적용

    → 순수하게 컴파일한 뒤, 클래스를 로딩하는 시점에 클래스 정보를 변경하는 방법

    → 단점 : 자바를 실행할 때 특별한 옵션( java -javaagent )을 통해 클래스 로더 조작기를 지정해야 하는데, 이 부분이 번거롭고 운영하기 어렵다.

    1. 런타임 적용

    스프링 AOP가 주로 사용하는 방법. A라는 클래스 타입의 Bean을 만들 때 A 타입의 Proxy Bean을 만들어 Proxy Bean이 Aspect 코드를 추가하여 동작하는 방법.

    스프링 Aop

    • 스프링 AOP는 프락시 기반의 AOP 구현체이다.
    • 프록시 객체를 사용하는 것은 접근 제어 및 부가 기능을 추가하기 위해서이다.
    • 스프링 AOP는 스프링 Bean에만 적용할 수 있다.
    • 모든 AOP 기능을 제공하는 것이 목적이 아닌, 중복 코드, 프록시 클래스 작성의 번거로움 등 흔한 문제를 해결하기 위한 솔루션을 제공하는 것이 목적이다.
    • 스프링 AOP는 순수 자바로 구현되었기 때문에 특별한 컴파일 과정이 필요하지 않다.

    Aop 구현

    builder 에 넣어줘야 한다

    implementation 'org.springframework.boot:spring-boot-starter-aop'
    
    //테스트에서 lombok 사용
    testCompileOnly 'org.projectlombok:lombok'
    testAnnotationProcessor 'org.projectlombok:lombok'
    

    간단한 예시

    //hello.aop.order 패키지와 하위 패키지
     @Pointcut("execution(* hello.aop.order..*(..))") //pointcut expression
     private void allOrder(){} //pointcut signature
    
     @Around("allOrder()")
    public Object doLog() {}
    

    Aop 주의점

    @Trace - 애노테이션으로 로그 출력하기 !!

    @Retry - 애노테이션으로 예외 발생시 재시도 하기 → 복구하는 것

    → retry 는 횟수의 제한을 꼭 줘야한다

    @Retry(value = 4)
    

     

    실무에서 주의

    1. 프록시와 내부 호출 문제

    스프링 → 프록시 방식의 AOP 사용 ⇒ AOP 적용하기 위해서는 항상 프록시 통해서 객체 호출해야 한다 ( 이렇게 해야 프록시 → advice → target 호출 )

    프록시 안거치고 객체 호출 → AOP 적용 X, advice 호출 X

    AOP 적용 → 스프링은 대상 객체 대신에 프록시를 스프링 빈으로 등록

    ⇒ 스프링은 의존관계 주입시에 항상 프록시 객체를 주입한다

    → 내부 호출은 프록시를 거치지 않는다. 따라서 어드바이스도 적용할 수 없다.

    @Slf4j
    @Component
    public class CallServiceV0 {
    
        public void external() {
            log.info("call external");
            /**
             * 메서드를 호출할 때 대상을 지정하지 않으면 앞에 자기 자신의 인스턴스를 뜻하는 this 가 붙게 된다
             */
            internal(); // 내부 메서드 호출 (this.internal()) -> 내부호출 : 프록시 안거침 : 어드바이스 적용 x
        }
    
        public void internal() {
            log.info("call internal");
        }
    }
    

    proxy 방식 AOP 한계 - 스프링은 프록시 방식의 AOP를 사용한다 → 얘는 메서드 내부 호출에 프록시를 적용할 수 없다.

    실제 코드에 AOP를 직접 적용하는 AspectJ 사용하면 이런 내부 호출 문제 발생안함. 왜냐 얘는 코드에 직접 AOP 적용 코드가 붙여야하는데, 얘는 로드 타임 위빙 등을 사용해야해서 JVM에 부담 → 그래서 사용 잘 안함

    내부 호출 문제 해결 방법

    public 에서 public을 메서드를 내부 호출하는 경우에 문제가 발생하는데, 이런 경우 고민을 해보기!!

    1. 자기 자신을 의존관계 주입
      1. 참고: 생성자 주입은 순환 사이클을 만들기 때문에 실패한다.
    2. 지연 조회 ( 생사자 주입 )
      1. 스프링 빈을 지연해서 조회하기 → ObjectProvider(Provider), ApplicationContext를 사용해서 지연 조회하기
        1. ApplicationContext 은 너무 많은 기능을 지원한다
    3. 구조 변경 ( 권장 ** )
      1. 가장 나은 대안은 내부 호출이 발생하지 않도록 구조를 변경

    프록시 기술과 한계

    1. JDK 동적 프록시
      1. 인터페이스가 필수고, 인터페이스 기반으로 프록시 생성한다
      2. 단점
        1. 구체 클래스로 타입 캐스팅 불가
    2. CGLIB
      1. 구체 클래스 기반으로 프록시 생성
    proxyTargetClass=false // JDK 동적 프록시를 사용해서 인터페이스 기반 프록시 생성
    proxyTargetClass=true // CGLIB를 사용해서 구체 클래스 기반 프록시 생성
    참고로 옵션과 무관하게 인터페이스가 없으면 JDK 동적 프록시를 적용할 수 없으므로 CGLIB를 사용한다
    

    정리 JDK 동적 프록시는 대상 객체인 MemberServiceImpl 로 캐스팅 할 수 없다. CGLIB 프록시는 대상 객체인 MemberServiceImpl 로 캐스팅 할 수 있다

    프록시 기술과 한계 → 의존관계 주입 ( 프록시 캐스팅이 영향을 줌 )

    구체타입으로 의존관계를 주입하면 문제가 된다

    정리 JDK 동적 프록시는 대상 객체인 MemberServiceImpl 타입에 의존관계를 주입할 수 없다. CGLIB 프록시는 대상 객체인 MemberServiceImpl 타입에 의존관계 주입을 할 수 있다.

    CGLIB 구체 클래스 기반 프록시 문제점

    1. 대상 클래스에 기본 생성자 필수
      1. 상속받으면 자식 클래스는 부모 클래스의 생성자를 호출하니까 → CGLIB 프록시는 대상 클래스를 상속 받고, 생성자에서 대상 클래스의 기본 생성자를 호출한다. 따라서 대상 클래스에 기본 생성자를 만들어야 한다
    2. 생성자 2번 호출 문제
      1. 실제 target 객체 생성시
      2. 프록시 객체를 생성할때 부모 클래스의 생성자 호출
    3. final 키워드 클래스, 메서드 사용 불가
      1. final 키워드가 클래스에 있으면 상속이 불가능하고, 메서드에 있으면 오버라이딩이 불가능하다. CGLIB는 상속을 기반으로 하기 때문에 두 경우 프록시가 생성되지 않거나 정상 동작하지 않는다.

    위 문제에 대한 스프링의 해결책

    → 스프링은 CGLIB 라이브러리를 스프링 내부에 함께 패키징해서 별도의 라이브러리 추가 없이 CGLIB를 사용할 수 있게 되었다. CGLIB springcore org.springframework

    1. 기본 생성자 문제 → objenesis 라는 특별한 라이브러리를 사용해서 기본 생성자 없이 객체 생성이 가능하다. ( 생성자 호출 없이 객체 생성을 돕는 라이브러리 )
    2. 생성자 2번 호출 → objenesis 라는 특별한 라이브러리 덕분에 가능해졌다. 이제 생성자가 1번만 호출된다
    3. 스프링 부트 2.0 버전부터 CGLIB를 기본으로 사용하도록 했다. 이렇게 해서 구체 클래스 타입으로 의존관계를 주입하는 문제를 해결했다.

    참고

    https://code-lab1.tistory.com/193

     

    참고

    김영한 선생님 인프런 - 고급편

    https://code-lab1.tistory.com/193

     

    [Spring] AOP(Aspect Oriented Programming)란? 스프링 AOP란?

    AOP (Aspect Oriented Programming)란? AOP는 Aspect Oriented Programming의 약자로 관점 지향 프로그래밍이라고 불린다. 관점 지향은 어떤 로직을 기준으로 핵심적인 관점, 부가적인 관점으로 나누어서 보고 그

    code-lab1.tistory.com

     

    'CS' 카테고리의 다른 글

    [CS] JPA 영속성 컨텍스트  (0) 2023.01.06
    [CS] 스프링부트를 쓰는 이유와 자동설정 원리  (0) 2023.01.05
    [CS] Bean  (0) 2023.01.04
    [CS] 멀티 스레드  (0) 2022.12.27
    [CS] 클라우드  (2) 2022.12.27
Designed by Tistory.