Group Study (2024-2025)/Spring 심화

[ Spring 심화 ] 7주차 - AOP(2)

다연리 2024. 12. 1. 22:52

분리한 트랜잭션 코드 → 기존 설계와 코드에는 영향을 주지 않는 형태로 제공되어야 함

5. 스프링 AOP

1. 자동 프록시 생성

프록시 팩토리 빈 방식의 접근 방법의 한계

  • 부가기능이 타깃 오브젝트마다 새로 만들어지는 문제 → ProxyFactoryBean어드바이스로 해결
  • 부가기능의 적용이 필요한 타깃 오브젝트마다 거의 비슷한 내용의 ProxyFactoryBean빈 설정정보 추가해주는 부분

중복 문제의 접근 방법

  • 런타임 코드 자동생성 기법을 통한 해결(프록시)
    • JDK의 다이내믹 프록시 → 특정 인터페이스를 구현한 오브젝트에 대해 프록시 역할을 해주는 클래스를 런타임시 내부적으로 만들어줌 → 클래스 소스는 남지 않지만 타깃 인터페이스의 모든 메소드를 구현하는 클래스가 만들어짐
    • 다이내믹 프록시의 역할 → 변하지 않는 타깃으로의 위임과 부가기능 적용 여부 판단
    • 변하는 부가기능 코드는 별도로 만들어서 다이내믹 프록시 생성 팩토리에 DI로 제공
    → 변하는 로직과 변하지 않는 기계적인 코드를 잘 분리해냄
  • → 의미 있는 부가기능인 트랜잭션 경계설정은 코드로, 기계적인 코드인 타깃 인터페이스 구현과 위임, 부가기능 연동 부분은 자동생성

⇒ 반복적인 프록시의 메소드 구현은 코드 자동생성 기법을 통해 해결 가능

  • 반복적인 ProxyFactoryBean 설정 문제
    • 설정 자동등록 기법으로는 한 번에 여러 개의 빈에 프록시를 적용할 수 x

빈 후처리기를 이용한 자동 프록시 생성기

  • 스프링은 컨테이너로서 제공하는 기능 중 변하지 않는 핵심적인 부분 외에는 대부분 확장할 수 있도록 확장 포인트 제공
  • 빈 후처리기 → DefaultAdvisorAutoProxyCreator
    • 어드바이저를 이용한 자동 프록시 생성기
    • 빈 후처리기를 빈으로 등록하면 스프링에 적용 가능
    • 빈 후처리기 빈으로 등록 → 스프링은 빈 오브젝트가 생성될 때마다 빈 후처리기에 보내서 후처리 작업 요청
    • 빈 후처리기는 빈 오브젝트의 프로퍼티를 강제로 수정할 수 있고 별도의 초기화 작업을 수행할 수 있음 → 스프링이 설정을 참고해서 만든 오브젝트가 아닌 다른 오브젝트를 빈으로 등록하는 것이 가능
    • 자동 프록시 생성 빈 후처리기: 스프링이 생성하는 빈 오브젝트의 일부를 프록시로 포장하고, 프록시를 빈으로 대신 등록할 수 있음
    • 빈 후처리기를 이용한 자동 프록시 생성 방법
      • DefaultAdvisorAutoProxyCreator 빈 후처리기 등록되어 있음 → 스프링은 빈 오브젝트를 만들 때마다 후처리기에게 빈을 보냄 → DefaultAdvisorAutoProxyCreator는 빈으로 등록된 모든 어드바이저 내의 포인트컷을 이용해 전달받은 빈이 프록시 적용 대상인지 확인 → 프록시 적용 대상이라면 내장된 프록시 생성기에게 현재 빈에 대한 프록시를 만들게 하고, 만들어진 프록시에 어드바이저를 연결해줌 → 빈 후처리기는 프록시가 생성되면 원래 컨테이너가 전달해준 빈 오브젝트 대신 프록시 오브젝트를 컨테이너에게 돌려줌 → 컨테이너는 최종적으로 빈 후처리기가 돌려준 오브젝트를 빈으로 등록하고 사용함
      • ⇒ 타깃 오브젝트에 자동으로 프록시가 적용되게 할 수 있음

확장된 포인트컷

  • 포인트컷의 역할 → 타깃 오브젝트의 메소드 중 어떤 메소드에 부가기능을 적용할지 선정해주는 역할 → 하지만 빈 후처리기를 이용한 프록시 자동생성에서 포인트컷이 등록된 빈 중에서 어떤 빈에 프록시를 적용할지를 선택
  • 포인트컷의 기능
    • 메소드 선별 → 클래스 필터는 모든 클래스를 다 받아주도록 만들어져 있음 → 클래스 종류 상관없이 메소드만 판별
    • ProxyFactoryBean에서 포인트컷 사용 → 이미 타깃이 정해져 있기 때문에 포인트컷은 메소드 선별만 해주면 됨
    • DefaultFactoryBean에서는 빈 후처리기 DefalutAdvisorAutoProxyCreator는 클래스와 메소드 선정 알고리즘을 모두 갖고 있는 포인트컷이 필요함

포인트컷 테스트

  • 포인트컷으로 해당 메소드 이름을 가진 클래스만 선정
  • 세 개의 클래스를 포인트컷을 적용할 타깃 오브젝트로 등록 → 각 메소드에 대한 어드바이스 적용 여부 확인
  • 클래스 필터를 통과하지 않는 타깃 오브젝트는 모든 메소드에 어드바이스가 적용되지 않음

2. DefaultAdvisorAutoProxyCreator의 적용

클래스 필터를 적용한 포인트컷 작성

  • 프로퍼티로 주어진 이름 패턴을 가지고 클래스 이름을 비교하는 ClassFilter추가
    • 디폴트 필터를 프로퍼티로 받은 클래스 이름을 이용해 필터로 만들어 덮어씌움

어드바이저를 이용하는 자동 프록시 생성기 등록

  • DefaultAdvisorAutoProxyCreator는 등록된 빈 중 Advisor 인터페이스를 구현할 것을 모두 찾음 → 생성되는 모든 빈에 대해 어드바이저의 포인트컷을 적용해보며 프록시 적용 대상을 선정함
    • 빈 클래스가 프록시 선정 대상이라면 → 프록시를 만들어 원래 빈 오브젝트와 바꿔치기 함
    • 원래 빈 오브젝트는 프록시 뒤에 연결돼서 프록시를 통해서만 접근 가능하게 바뀜 → 타깃 빈에 의존한다고 정의한 다른 빈들은 프록시 오브젝트를 대신 DI 받게 됨
    • 등록할때 프로퍼티에 DefaultAdvisorAutoProxyCreator 등록만 하면 됨 → id 애트리뷰트가 없고 class만 있음 → 다른 빈에서 참조되거나 코드에서 빈 이름으로 조회될 필요가 없는 빈이기 때문

포인트컷 등록

  • 포인트컷 빈을 등록

어드바이스와 어드바이저

  • 어드바이저를 이용하는 자동 프록시 생성기인 DefaultAdvisorAutoProxyCreator에 의해 빈이 자동수집되고, 프록시 대상 선정 과정에 참여하고, 자동생성된 프록시에 다이내믹하게 DI돼서 동작하는 어드바이저가 됨

ProxyFactoryBean의 제거와 서비스 빈의 원상복구

  • 이제 프록시 빈을 등록하지 않음 → 프록시에 DI 돼서 간접적으로 사용돼야 했던 userServiceImpl 빈의 아이디를 userService로 되돌려 놓을 수 있음 → 명시적인 프록시 팩토리 빈을 등록하지 않아도 됨
  • UserService와 관련된 빈 설정은 userService 빈 하나로 충분함

자동 프록시 생성기를 사용하는 테스트

  • 자동 프록시 생성기를 적용한 후에는 팩토리 빈이 존재하지 않고, 프록시 오브젝트만 남아 있음
  • 클래스 이름을 포인트컷이 선정해줄 수 있도록 ServiceImpl로 끝나도록 만들어 줌 → 이 클래스를 빈으로 등록
  • @Autowired 로 트랜잭션 테스트에 사용할 빈의 타입(UserService, testUserService)등록

자동생성 프록시 확인

  • 특별한 빈 등록과 포인트컷 작성만으로 프록시가 자동으로 만들어질 수 있게 함

3. 포인트컷 표현식을 이용한 포인트컷

포인트컷 표현식: 스프링은 포인트컷의 클래스와 메소드를 선정하는 알고리즘을 작성할 수 있는 방법을 제공함, 일종의 표현식 언어를 사용해서 포인트컷을 작성할 수 있음

포인트컷 표현식

  • AspectExpressionPointcut: 포인트컷 표현식을 지원하는 포인트컷 적용
    • Pointcut 인터페이스를 구현해야 하는 스프링의 포인트컷은 클래스 필터, 메소드 매처를 제공해야 함
    • AspectExpressionPointcut은 클래스와 메소드의 선정 알고리즘을 포인트컷 표현식을 이용해 한 번에 지정할 수 있게 해줌

포인트컷 표현식 문법

  • 포인트컷 지시자를 이용해 AspectJ 표현식을 작성
  • execution() → 포인트컷 지시자 중 가장 대표적으로 사용되는 것
  • execution([접근제한자 패턴] 타입패턴 [타입패턴.] 이름패턴 (타입패턴 | * .. * )

포인트컷 표현식 테스트

  • 리턴 값의 타입에 대한 제한을 없애고 어떤 리턴 타입을 가졌든 상관없이 선정하도록 만드려면 * 와일드카드 사용하면 됨
  • 모든 선정조건을 다 없애고 모든 메소드를 다 허용하는 포인트컷이 필요하다면 메소드 이름도 와일드카드로 바꾸면 됨
  • 헬퍼 메소드는 타깃 클래스의 메소드에 대해 포인트컷 선정 여부를 검사함
  • Runtime예외 선언을 포인트컷의 조건으로 사용할 수 있음

포인트컷 표현식을 이용하는 포인트컷 적용

  • bean() → 스프링에서 사용될 때 빈의 이름으로 비교
  • 특정 애노테이션이 타입, 메소드, 파라미터에 적용되어 있는 것을 보고 메소드를 선정하게 하는 포인트컷도 만들수 있음 → @Transactional 이 적용된 메소드 선정
  • 포인트컷을 통해 자동으로 선정해서 부가기능을 제공해주는 방식
  • 포인트컷 표현식 적용 방법
    • 포인트컷 표현식은 AspectJExpressionPointcut빈 등록하고 expression 프로퍼티에 넣어주면 됨

타입 패턴과 클래스 이름 패턴

  • 포인트컷 표현식 적용 이전 → 클래스 이름의 패턴을 이용해 타깃 빈을 선정하는 포인트컷 사용
  • ServiceImpl로 끝나는 클래스 이름을 가진 빈을 선정하도록 포인트컷 구성
  • 포인트컷 표현식의 클래스 이름에 적용되는 패턴 → 타입 패턴
    • interface 타입 패턴 명시 → 구현체(class)들도 타입 패턴 조건 충족 → 포인트컷에 의해 해당 클래스들이 선정됨

4. AOP란 무엇인가?

UserService에 트랜잭션을 적용해온 과정

트랜잭션 서비스 추상화

  • 트랜잭션 경계설정 코드를 비즈니스 로직에 담음
    • 문제: 특정 트랜잭션 기술에 종속되는 코드가 돼버림 → 기술을 바꾸면 코드도 수정해야 함
  • 해결: 서비스 추상화 기법 적용
    • 비즈니스 로직 코드는 구체적인 트랜잭션 처리 방법과 서버환경에 종속되지 않음
    • 구체적인 구현 내용을 담은 의존 오브젝트는 런타임 시 다이내믹하게 연결해 주는 DI활용
  • 트랜잭션 추상화란 인터페이스와 DI를 통해 무엇을 하는지는 남기고, 그것을 어떻게 하는지 분리 → 비즈니스 로직 코드에는 영향을 주지 않고 독립적으로 변경할 수 있게 됨

프록시와 데코레이터 패턴

  • 비즈니스 로직 코드에 트랜잭션을 적용하고 있는 코드가 드러남
    • 문제: 트랜잭션은 비즈니스 로직을 담은 메소드에 필요함, 트랜잭션의 경계설정을 담당하는 코드의 특성 때문에 단순한 추상화와 메소드 추출 방법으로는 트랜잭션 코드를 제거할 수 없음
  • 해결: DI를 이용해 데코레이터 패턴 적용
    • 클라이언트가 인터페이스와 DI를 통해 접근하도록 설계하고, 데코레이터 패턴을 적용해 비즈니스 로직을 담은 클래스의 코드에는 전혀 영향을 주지 않으며 트랜잭션이라는 부가기능을 자유롭게 부여할 수 있는 구조를 만듬
    • 트랜잭션 처리 코드는 데코레이터에 담겨서 클라이언트와 비즈니스 로직을 담은 타깃 클래스 사이에 존재하게 만듬 → 클라이언트가 프록시 역할을 하는 트랜잭션 데코레이터를 거쳐서 타깃에 접근할 수 있게 됨
    • 클라이언트가 요청하면 비즈니스 로직에 들어가기 전에 트랜잭션 데코레이터를 거쳐서 접근한다는 건가?
  • 비즈니스 로직 코드는 트랜잭션과 같은 성격이 다른 코드로부터 자유로워졌고, 독립적으로 로직을 검증하는 고립된 단위 테스트를 만들 수 있게 됨

다이내믹 프록시와 프록시 팩토리 빈

  • 프록시를 이용해 비즈니스 로직 코드에서 트랜잭션 코드는 모두 제거할 수 있었지만, 비즈니스 로직 인터페이스의 모든 메소드마다 트랜잭션 기능을 부여하는 코드를 넣어 프록시 클래스를 만드는 작업이 오히려 큰 짐이 됨 → 트랜잭션 기능을 부여하지 않아도 되는 메소드도 프록시로서 위임 기능이 필요해 모두 구현을 해 줘야 함
  • JDK 다이내믹 프록시 기술로 프록시 클래스 없이도 프록시 오브젝트를 런타임 시에 만들어줘서 해결 → 하지만 동일한 기능의 프록시를 여러 오브젝트에 적용할 경우 오브젝트 단위로는 중복이 일어나는 문제는 해결하지 못함
  • 프록시 팩토리 빈(JDK 다이내믹 같은 프록시 기술 추상화)을 이용해서 다이내믹 프록시 생성 방법에 DI 도입
    • 내부적으로 템플릿/콜백 패턴을 활용하는 스프링 프록시 팩토리 빈 덕분에 부가기능을 담은 어드바이스와 부가기능 선정 알고리즘을 담은 포인트컷을 프록시에서 분리할 수 있었고, 여러 프록시에서 공유해서 사용할 수 있게 됨

자동 프록시 생성 방법과 포인트컷

  • 트랜잭션 적용 대상이 되는 빈마다 모두 프록시 팩토리 빈을 설정해 줘야 함

→ 컨테이너 초기화 시점에서 자동으로 프록시를 만들어주는 방법 도입 → 트랜잭션 부가기능을 어디에 적용하는지에 대한 정보를 포인트컷이라는 독립적인 정보로 완전히 분리 할 수 o

  • 포인트컷 표현식으로 적용 대상을 쉽게 선택할 수 있게 됨

부가기능의 모듈화

  • 소프트웨어 개발의 원칙: 관심사가 같은 코드를 분리해 한데 모으는 것
  • 관심사가 같은 코드를 객체지향 설계 원칙에 따라 분리하고, 낮은 결합도를 가진 채로 독립적이고 유연하게 확장할 수 있는 모듈로 만드는 것이 초난감 DAO로 시작해서 지금까지 해온 작업
  • 하지만 트랜잭션 적용 코드는 기존 방법으로는 독립된 모듈로 만드는 것이 불가능
    • 트랜잭션 경계설정 기능은 다른 모듈의 코드에 부가적으로 부여되는 기능이기 때문
  • 트랜잭션 같은 부가기능은 모듈화하기 힘듬 → 부가기능이라 스스로 독립적인 방식으로 존재해서는 적용되기 어렵기 때문
  • 트랜잭션 부가기능은 트랜잭션 기능을 추가해줄 다른 대상(타깃)이 존재해야만 의미가 있음 → 타깃의 코드 안에 긴밀하게 연결되어 있어야 함(독립적으로 존재하는것 불가능)
  • TransactionAdvice → 핵심기능을 담은 코드와 설정에 영향을 주지 않아도 됨
    • 독립적으로 모듈화 됨 → 중복 x, 변경이 필요한 부분만 수정
    • 포인트컷으로 부가기능을 부여할 대상 선정

AOP

  • 이거 긁어서 챗지피티한테 물어보기 (@Transactional,예외처리 엿던걸로 기억함..)
  • 트랜잭션 경계설정은 기존의 객체지향 설계 패러다임과는 구분되는 새로운 특성이 있음
  • aspect
    • 그 자체로 애플리케이션의 핵심기능을 담고 있지 않지만, 애플리케이션을 구성하는 중요한 한 가지 요소이고, 핵심기능에 부가되어 의미를 갖는 특별한 모듈
    • 구성: 어드바이스(부가될 기능 정의된 코드), 포인트컷(어드바이스 어디에 적용할지 결정)
    • 애플리케이션을 구성하는 한 가지 독립된 측면 → 핵심기능 코드 사이에 침투한 부가기능을 독립적인 aspect로 구분
    • 런타임 시 aspect는 자기가 필요한 위치에 다이내믹하게 참여
  • Aspect Oriented Programming(AOP = 관점 지향 프로그래밍)
    • 애플리케이션의 핵심적인 기능에서 부가적인 기능을 분리해서 aspect라는 모듈로 만들어서 설계하고 개발하는 방법
    • AOP는 OOP를 돕는 보조적인 기술
    • AOP는 aspect를 분리함으로써 핵심기능을 설계하고 구현할 때 객체지향적인 가치를 지킬 수 있도록 도와줌
    • 애플리케이션을 다양한 측면에서 독립적으로 모델링하고, 설계하고, 개발할 수 있도록 만들어 주는 것 → 애플리케이션을 다양한 측면에서 개발할 수 있게 해줌
    • 애플리케이션을 특정 관점(ex/트랜잭션) 기준으로 바라볼 수 있게 해줌

5. AOP 적용기술

프록시를 이용한 AOP

  • 스프링은 프록시로 만들어서 DI로 연결된 빈 사이에 적용해 타깃의 메소드 호출 과정에 참여해서 부가기능을 제공하도록 만듬
  • 스프링 AOP의 부가기능을 담은 어드바이스가 적용되는 대상 → 오브젝트의 메소드 → 프록시 방식을 사용 → 메소드 호출 과정에 참여해서 부가기능 제공
  • 프록시: 독립적으로 개발한 부가기능 모듈을 다양한 타깃 오브젝트의 메소드에 다이내믹하게 적용하는 역할 → 스프링 AOP = 프록시 방식의 AOP(간접적)

바이트코드 생성과 조작을 통한 AOP

  • AspectJ → 프록시를 사용하지 않는 AOP 기술
  • 타깃 오브젝트를 뜯어고쳐서 부가 기능을 직접 넣어주는 직접적인 방법 사용
  • 컴파일된 타깃의 클래스 파일 직접 수정 or 클래스가 JVM에 로딩되는 시점을 가로채서 바이트코드를 조작하는 방법 사용 → 트랜잭션 코드가 UserService 클래스에 비즈니스 로직과 함께 있었을 때처럼 만듬
    • 사용하는 이유?
      1. 바이트코드를 조작해 타깃 오브젝트를 직접 수정하면 자동 프록시 생성 방식을 사용하지 않아도 AOP 적용 가능 → 스프링 컨테이너가 없어도 손쉽게 AOP 적용 가능
      2. 프록시 방식보다 훨씬 강력하고 유연한 AOP 가능
        • 프록시 방식 → 부가기능을 부여할 대상 = 클라이언트가 호출할 때 사용하는 메서드
        • 바이트코드 조작 → 오브젝트 생성, 필드값의 조회와 조작,스태틱 초기화 등 다양한 작업에 부가 기능을 부여해줄 수 있음
        • 타깃 오브젝트가 생성되는 순간, private class에 부가기능 부여 가능

6. AOP의 용어

  • 타깃: 부가기능을 부여할 대상. 핵심 기능 담은 클래스 or 다른 부가기능 제공하는 프록시 오브젝트
  • 어드바이스: 타깃에게 제공할 부가기능을 담은 모듈. 어드바이스는 오브젝트로 정의하기도 하지만 메소드 레벨에서도 정의할 수도 있음
  • 조인포인트: 어드바이스가 적용될 수 있는 위치. 스프링 프록시 AOP에서 조인 포인트 → 메소드의 실행 단계
  • 포인트컷: 어드바이스를 적용한 조인 포인트를 선별하는 작업 or 기능을 정의한 모듈
    • 스프링 AOP의 조인 포인트: 메소드의 실행 단계. 타깃 오브젝트가 구현한 인터페이스의 모든 메소드는 조인 포인트가 됨
  • 포인트컷: 어드바이스를 적용할 조인 포인트를 선별하는 작업 or 기능을 정의한 모듈
    • 스프링 AOP의 조인 포인트 → 메소드를 선정하는 기능
  • 프록시
    • 클라이언트와 타깃 사이에 투명하게 존재하면서 부가기능을 제공하는 오브젝트
    • DI를 통해 타깃 대신 클라이언트에게 주입되며, 클라이언트의 메소드 호출을 대신 받아서 타깃에 위임해 주면서, 그 과정에서 부가기능 부여
  • 어드바이
    • 포인트컷과 어드바이스를 하나씩 갖고 있는 오브젝트
    • 스프링은 자동 프록시 생성기가 어드바이저를 AOP 작업의 정보로 활용함
  • 애스펙트
    • AOP의 기본 모듈
    • 한개 이상의 포인트컷과 어드바이스의 조합으로 만들어짐
    • 보통 싱글톤 형태의 오브젝트로 존재

7. AOP 네임스페이스

  • 스프링 AOP를 적용하기 위해 추가했던 빈들 → 스프링 컨테이너에 의해 자동으로 인식돼서 특별한 작업을 위해 사용됨
  • 스프링의 프록시 방식 AOP를 적용하려면 자동 프록시 생성기, 어드바이스, 포인트컷, 어드바이저

AOP 네임스페이스

  • aop 스키마에 정의된 태그를 사용해서 설정 파일에 등록해서 사용