Skip to content

Latest commit

 

History

History
278 lines (210 loc) · 25.3 KB

6. AOP.md

File metadata and controls

278 lines (210 loc) · 25.3 KB

AOP

p.410) 트랜잭션 분리에 따른 테스트 수정

  • @Autowired는 기본적으로 타입을 이용해 빈을 찾지만 만약 타입으로 하나의 빈을 결정할 수 없는 경우에는 필드 이름을 이용해 빈을 찾는다.

P.413) 트랜잭션 경계설정 코드 분리의 장점

  • 첫째, 이제 비즈니스 로직을 담당하고 있는 UserServiceImple의 코드를 작성할 떄는 트랜잭션과 같은 기술적인 내용에는 전혀 신경 쓰지 않아도 된다.
  • 트랜잭션은 DI를 이용해 UserServiceTx와 같은 트랜잭션 기능을 가진 오브젝트가 먼저 실해오디도록 만들기만 하면 된다. 따라서 언제든지 트랜잭션을 도입할 수 있다.
  • 두 번째 장점은 비즈니스 로직에 대한 테스트를 손쉽게 만들어낼 수 있다는 것이다.

p.413) 고립된 단위 테스트

  • 가장 편하고 좋은 테스트 방법은 가능한 작은 단위로 쪼개서 테스트하는 것이다.
  • 작은 단위의 테스트가 좋은 이유는 테스트가 실패했을 때 그 원인을 찾기 쉽기 떄문이다. 반대로 테스트에서 오류가 발견됐을 떄 그 테스트가 진행되는 동안 실행된 코드의 양이 많다면 그 원인을 찾기가 매우 힘들어질 수도 있다.
  • 또한 테스트 단위가 작아야 테스트의 의도나 내용이 분명해지고, 만들기도 쉬워진다. 테스트할 대상이 크고 복잡하면 테스트를 만들기도 그만큼 어렵고, 만들었다 해도 충분하지 못할 수 있다.
  • 클래스 하나가 동작하도록 테스트를 만드는 것과 클래스 수십 개가 얽히고 설혀서 동작하도록 만드는 것 중에서 어떤 것이 논리적인 오류를 찾기 쉬울지는 분명하다.
  • 테스트 대상의 단위가 커지면 테스트를 만들기도 쉽지 않다.
  • 논리적인 오류가 발생해서 결과가 바르게 나오지 않았을 떄 그 원인을 찾기도 어려워진다.
  • 어쩌면 테스트가 실패한 코드를 살펴보는 것만으로는 충분하지 못해서, 디버거를 사용해 한 줄씩 실행해보면서 어느 부분에 오류가 있는지 일일이 확인해봐야 할 수도 있다.
  • 차라리 처음부터 작은 단위로 테스트하면서 진행해왔다면, 나중에 덩치가 커져도 어렵지 않게 오류를 찾아낼 수 있다. 작은 단위의 테스트로 검증한 부분은 제외하고 접근할 수 있기 때문이다.

따라서 테스트는 작은 단위로 하면 좋다. 하지만 작은 단위로 테스트하고 싶어도 그럴 수 없는 경우가 많다. 테스트 대상이 다른 오브젝트와 환경에 의존하고 있다면 작은 단위의 테스트가 주는 장점을 얻기 힘들다.

p.415)

  • 테스트의 대상이 환경이나, 외부 서버, 다른 클래스의 코드에 종속되고 영향을 받지 않도록 고립시킬 필요가 있다.

p.420)

  • 하지만 실수로 사용될 위험이 있으므로 unsupportedOperationException을 던지게 해서 지원하지 않는 기능이라는 예외가 발생하도록 만드는게 좋다.

p.421)

  • 컨테이너에서 가져온 UserService 오브젝트는 DI를 통해서 많은 의존오브젝트와 서비스, 외부 환경에 의존하고 있었다. 이제는 완전히 고립돼서 테스트만을 위해 독립적으로 동작하는 테스트 대상을 사용할 것이기 때문에 스프링 컨테이너에서 빈을 가져올 필요가 없다.

p.423)

  • 고립된 테스트를 하면 테스트가 다른 의존 대상에 영향을 받을 경우를 대비해 복잡하게 준비할 필요가 없을 뿐만 아니라, 테스트 수행 성능도 크게 향상된다. 테스트가 빨리 돌아가면 부담 없이 자주 테스트를 돌려볼 수

p.423)

  • 테스트 대상 클래스를 목 오브젝트 등의 테스트 대역을 이용해 의존 오브젝트나 외부의 리소스를 사용하지 않도록 고립시켜서 테스트 하는 것을 단위 테스트라고 부르겠다.
  • 반면에 두 개 이상의, 성격이나 계층이 다른 오브젝트가 연동하도록 만들어 테스트하거나, 또는 외부의 DB나 파일, 서비스 등의 리소스가 참여하는 테스트는 통합 테스트라고 부르겠다.
    • 통합 테스트란 두 개 이상의 단위가 결합해서 동작하면서 테스트가 수행되는 것이라고 보면 된다.
  • 스프링의 테스트 컨텍스트 프레임워크를 이용해서 컨텍스트에서 생성되고 DI된 오브젝트를 테스트하는 것도 통합 테스트다.

단위 테스트와 통합 테스트 중에서 어떤 방법을 쓸지는 몇 가지 가이드라인을 살펴보자.

  • 항상 단위 테스트를 먼저 고려한다.
  • 하나의 클래스나 성격과 목적이 같은 긴밀한 클래스 몇 개를 모아서 외부와의 의존관계를 모두 차단하고 필요에 따라 스텁이나 목 오브젝트 등의 테스트 대역을 이용하도록 테스트를 만든다. 단위 테스트는 작성도 간단하고 실행 속도도 빠르며 테스트 대상 외의 코드나 환경으로부터 테스트 결과의 영향을 받지도 않기 때문에 가장 빠른 시간에 효과적인 테스트를 작성하기에 유리하다.
  • 외부 리소스를 사용해야만 가능한 테스트는 통합 테스트로 만든다.
  • 단위 테스트로 만들기가 어려운 코드도 있다. 대표적인게 DAO다. DAO는 그 자체로 로직을 담고 있기보다는 DB를 통해 로직을 수행하는 인터페이스와 같은 역할을 한다. SQL을 JDBC를 통해 실행하는 코드만으로는 고립된 테스트를 작성하기가 힘들다. 작성한다고 해도 가치가 없는 경우가 대부분이다. 따라서 DAO는 DB까지 연동하는 테스트로 만드는 편이 효과적이다. DB를 사용하는 테스트는 DB에 테스트 데이터를 준비하고, DB에 직접 확인을 하는 등의 부가적인 작업이 필요하다.
  • DAO 테스트는 DB라는 외부 리소스를 사용하기 때문에 통합 테스트로 분류된다. 하지만 코드에서 보자면 하나의 기능 단위를 테스트하는 것이기도 하다. DAO를 테스트를 통해 충분히 검증해두면, DAO를 이용하는 코드는 DAO 역할을 스텁이나 목 오브젝트로 대체해서 테스트할 수 있다. 이후 실제 DAO와 연동했을 때도 바르게 동작하리라고 확신할 수 있다. 물론 각각의 단위 테스트가 성공했더라도 여러 개의 단위를 연결해서 테스트 하면 오류가 발생할 수도 있다. 하지만 충분한 단위 테스트를 거친다면 통합 테스트에서 오류가 발생할 확률도 줄어들고 발생한다 하더라도 쉽게 처리할 수 있다.
  • 여러 개의 단위가 의존관계를 가지고 동작할 때를 위한 통합 테스트는 필요하다. 다만,단위 테스트를 충분히 거쳤다면 통합 테스트의 부담은 상대적으로 줄어든다.
  • 단위 테스트를 만들기가 너무 복잡하다고 판단되는 코드는 처음부터 토압 테스트를 고려해 본다. 이때도 통합 테스트에 참여하는 코드 중에서 가능한 많은 부분을 미리 단위 테스트로 검증해두는 게 유리하다.
  • 스프링 테스트 컨텍스트 프레임워크를 이용하는 테스트는 통합 테스트다. 가능하면 스프링의 지원 없이 직접 코드레벨의 DI를 사용하면서 단위 테스트를 하는게 좋겠지만 스프링의 설정 자체도 테스트 대상이고, 스프링을 이용해 좀 더 추상적인 레벨에서 테스트해야 할 경우도 종종 있다. 이럴 떈 스프링 테스트 컨텍스트 프레임워크를 이용해 통합 테스트를 작성한다.

코드를 작성하면서 테스트는 어떻게 만들 수 있을까를 생각해보는 것은 좋은 습관이다. 테스트하기 편하게 만들어진 코드는 깔끔하고 좋은 코드가 될 가능성이 높다. 만약 DI도 사용하지 않고, 비즈니스 로직과 로우레벨의 기술과 외부 환경과 성격이 다른 데이터 엑세스 기능들이 한데 모여 강하게 결합되어있는 코드였다면, 과연이런 테스트를 만들 수 나 있었을까?

p.426)

Mockito 프레임워크

  • Mockito와 같은 목 프레임워크의 특징은 목 클래스를 일일이 준비해둘 필요가 없다는 점이다. 간단한 메소드 호출만으로 다이내믹하게 특정 인터페이스를 구현한 테스트용 목 오브젝트를 만들 수 있다.
  • UserDao 인터페이스를 구현한 테스트용 목 오브젝트는 다음과 같이 Mockito의 스태틱 메소드를 한 번 호출해주면 만들어진다. mock() 메소드는 org.mockiot.Matchers 클래스에 정의된 스태틱 메소드다. 스태틱 임포트를 사용해 로컬 메소드처럼 호출하게 하면 편리하다.

p.427)

Mockito 목 오브젝트는 다음의 네 단계를 거쳐서 사용하면 된다. 두 번째와 네 번째는 각각 필요한 경우에만 사용할 수 있다.

  • 인터페이스를 이용해 목 오브젝트를만든다.
  • 목 오브젝트가 리턴할 값이 있으면 이를 지정해준다. 메소드가 호출되면 예외를 강제로 던지게 만들 수 있다.
  • 테스트 대상 오브젝트에 DI해서 목 오브젝트가 테스트 중에 사용되도록 만든다.
  • 테스트 대상 오브젝트를 사용한 후에 목 오브젝트의 특정 메소드가 호출됐는지, 어떤 값을 가지고 몇 번 호출됐는지를 검증한다.

p.430)

  • 이렇게 마치 자신잌 클라이언트가 사용하려고 하는 실제 대상인 것처럼 위장해서 클라이어트의 요청을 받아주는 것을 대리자, 대리인 과 같은 역할을 한다고 해서 프록시라고 부른다.
  • 그리고 프록시를 통해 최종적으로 요청을 위임받아 처리하는 실제 오브젝트를 타깃 또는 실체라고 부른다.

프록시는 사용 목적에 따라 두 가지로 구분할 수 있다.

  • 첫째는 클라이언트가 타깃에 접근하는 방법을 제어하기 위해서다.
  • 두 번째는 타깃에 부가적인 기능을 부여해주기 위해서다. 두 가지 모두 대리 오브젝틀는 개념의 프록시를 두고 사용한다는 점은 동일하지만, 목적에 따라서 디자인 패턴에서는 다른 패턴으로 구분한다.

p.431)

데코레이터 패턴

데코레이터 패턴은 타깃에 부가적인 기능을 런타임 시 다이내믹하게 부여해주기 위해 프록시를 사용하는 패턴을 말한다.

다이내믹하게 기능을 부가한다는 의미는 컴파일 시점, 즉 코드상에서는 어떤 방법과 순서로 프록시와 타깃이 연결되어 사용되는지 정해 져 있지 않다는 뜻이다. 이 패턴의 이름이 데코레이터라고 불리는 이유는 마치 제품이나 케익 등을 여러 겹으로 포장하고 그 위에 장식을 붙이는 것처럼 실제 내용물은 동일하지만 부가적인 효과를 부여해줄 수 있기 때문이다. 따라서 데코레이터 패턴에서는 프록시가 꼭 한개로 제한되지 않는다. 프록시가 직접 타깃을 사용하도록 고정시킬 필요도 없다. 이를 위해 데코레이터 패턴에서는 같은 인터페이스를 구현한 타겟 과 여러 개의 프록시를 사용할 수 있다. 프록시가 여러 개인 만큼 순서를 정해서 단계적으로 위임하는 구조로 만들면 된다.

프록시로서 동작하는 각 데코리에터는 위임하는 대상에도 인터페이스로 접근하기 때문에 자신이 최종 타깃으로 위임하는지, 아니면 다음 단계의 데코레이터 프록시로 위임하는지 알지 못한다. 그래서 데코레이터의 다음 위임 대상은 인터페이스로 선언하고 생성자나 수정자 메소드를 통해 위임 대상을 외부에서 런타임 시에 주입받을 수 있도록 만들어야 한다.

자바 IO 패키지의 InputStream과 OutPutStream 구현 클래스는 데코레이터 패턴이 사용된 대표적인 예다. 다음 코드는 InputStream이라는 인터페이스를 구현한 타깃인 FileInputStream에 버퍼 읽기 기능을 제공해주는 BufferedInputStream이라는 데코레이터를 적용한 예다.

InputStream is = new BufferedInputStream(new FileInputStream("a.txt"))

인터페이스를 통한 데코리에터 정의와 런타임 시의 다이내믹한 구성 방법은 스프링의 DI를 이용하면 아주 편리하다. 데코레이터 빈의 프로퍼티로 같은 인터페이스를 구현한 다른 데코레이터 또는 타깃 빈을 설정하면 된다.

p.433)

프록시 패턴

일반적으로 사용하는 프록시라는 용어와 디자인 패턴에서 말하는 프록시 패턴은 구분할 필요가 있다.

  • 전자는 클라이언트와 사용 대상 사이에 역할을 맡은 오브젝트를 두는 방법을 총칭한다면,
  • 후자는 프록시를 사용하는 방법 중 타깃에 대한 접근 방법을 제어하려는 목적을 가진 경우를 가리킨다.

프록시 패턴의 프록시는 타깃의 기능을 확장하거나 추가하지 않는다. 대신 클라이언트가 타깃에 접근하는 방식을 변경해준다. 타깃 오브젝트를 생성하기가 복잡하거나 당장 필요하지 않은 경우에는 꼭 필요한 시점까지 오브젝트를 생성하지 않는 편이 좋다.

그런데 타깃 오브젝트에 대한 레퍼런스가 미리 필요할 수 있다. 이럴 때 프록시 패턴을 적용하면 된다. 클라이언트에게 타깃에 대한 레퍼 런스를 넘겨야 하는데, 실제 타깃 오브젝트는 만드는 대신프록시를 넘겨주는 것이다. 그리고 프록시의 메소드를 통해 타깃을 사용하료고 시도하면, 그때 프록시가 타깃 오브젝트를 생성하고 요청을 위임해주는 식이다.

만약 레퍼런스는 갖고 있지만 끝까지 사용하지 않거나, 많은 작업이 진행된 후에 사용되는 경우라면, 이렇게 프록시를 통해 생성을 최대한 늦춤으로써 얻는 장점이 많다.

또는 특별한 상황에서 타깃에 대한 접근권한을 제어하기 위해 프록시 패턴을 사용할 수 있다. 만약 수정 가능한 오브젝트가 있는데, 특정 레이어로 넘어가서는 읽기전용으로만 동작하게 강제해야 한다고 하자. 이럴 때는 오브젝트의 프록시를 만들어서 사용할 수 있다. 프록시의 특정 메소드를 사용하려고 하면 접근이 불가능하다고 예외를 발생시키면 된다. Collectons의 ummodifiableCollection()을 통해 만들어지는 오브젝트가 전혀적인 접근권한 제어용 프록시라고 볼 수 있다. 파라미터로 전달된 Collection 오브젝트의 프록시를 만들어서, add()나 remove() 같이 정보를 수정하는 메소드를 호출할 경우 UnsupportedOperationException 예외가 발생하게 해준다.

이렇게 프록시 패턴은 타깃의 기능 자체에는 관여하지 않으면서 접근하는 방법을 제어해주는 프록시를 이용하는 것이다. 구조적으로 보자면 프록시와 데코레이터는 유사하다. 다만 프록시는 코드에서 자신이 만들거나 접근할 타깃 클래스 정보를 알고 있는 경우가 많다. 생성을 지연하는 프록시라면 구체적인 생성 방법을 알아야 하기 때문에 타깃 클래스에 대한 직접적인 정보를 알아야 한다.** 물론 프록시 패턴이라고 하더라도 인터페이스를 통해 위임하도록 만들 수도 있다. 인터페이스를 통해 다음 호출 대상으로접근하게 하면 그 사이에 다른 프록시나 데코레이터가 계속 추가될 수 있기 때문이다.

타깃과 동일한 인터페이스를 구현하고 클라이언트와 타깃 사이에 존재하면서 기능의 부가 또는 접근 제어를 담당하는 오브젝트를 모두 프록시라 부를 수 있다.

프록시는 기존 코드에 영향을 주지 않으면서 타깃의 기능을 확장하거나 접근 방법을 제어할 수 있는 유용한 방법이다.

p.436)

프록시 만들기 번거러운 이유?

  • 첫째는 타깃의 인터페이스를 구현하고 위임하는 코드를 작성하기가 번거럽다는 점이다. 부가기능이 필요 없는 메소드도 구현해서 타깃으로 위임하는 코드를 일일이 만들어줘야 한다. 복잡하진 않지만 인터페이스의 메소드가 많아지고 다양해지면 상당히 부담스러운 작업이 될 것이다. 또, 타깃 인터페이스의 메소드가 추가되거나 변경될 때마다 함께 수정해줘여 한다는 부담도 있다.
  • 두 번째 문제점은 부가기능 코드가 중복될 가능성이 많다는 것이다. 메소드가 많아지고 트랜잭션 적용의 비율이 높아지면 트랜잭션 기능을 제공하는 유사한 코드가 여러 메소드에 중복돼서 나타날 것이다.

p. 437)

리플렉션

다이내믹 프록시는 리플렉션 기능을 이용해 프록시를 만들어준다. 리플렉션은 자바의 코드 자체를 추상화해서 접근하도록 만든 것이다.

  • 다이내믹 프록시는 프록시 팩토리에 의해 런타임 시 다이내믹하게 만들어지는 오브젝트다.
  • 클라이언트는 다이내믹 프록시 오브젝트를 타깃 인터페이스를 통해 사용할 수 있다. 이 덕분에 프록시를 만들 때 인터페이스를 모두 구현해가면서 클래스를 정의하는 수고를 덜 수 있다.
  • 프록시 팩토리에게 인터페이스 정보만 제공해주면 해당 인터페이스를 구현한 클래스의 오브젝트를 자동으로 만들어주기 때문이다.

p.444)

다이내믹 프록시의 확장

  • 다이내믹 프록시가 만들어질 때 추가된 메소드가 자동으로 포함될 것이고, 부가기능은 invoke() 메소드에서 처리되기 때문이다.

p.449)

다이내믹 프록시를 위한 팩토리 빈

  • 스프링은 내부적으로 리플렉션 API를 이용해서 빈 정의에 나오는 클래스 이름을 가지고 빈 오브젝트를 생성한다. 문제는 다이내믹 프록시 오브젝 트는 이런 식으로 프록시 오브젝트가 생성되지 않는 다는 점이다.

팩토리 빈

  • 사실 스프링은 클래스 정보를 가지고 디폴트 생성자를 통해 오브젝트를 만드는 방법 외에도 빈을 만들 수 있는 여러가지 방법을 제공한다. 대표적으로 팩토리 빈을 이용한 빈 생성 방법을 들 수 있다.
  • 팩토리 빈이란 스프링을 대신해서 오브젝트의 생성로직을 담당하도록 만들어진 특별한 빈을 말한다.
  • 팩토리 빈을 만드는 방법에는 여러 가지가 있는데, 가장 간단한 방법은 스프링의 FactoryBean이라는 인터페이스를 구현하는 것이다.
  • FactoruBean 인터페이스를 구현한 클래스를 스프링의 빈으로 등록하면 팩토리 빈으로 동작한다.

p.450)

사실 스프링은 private 생성자를가진 클래스도 빈으로 등록해주면 리플렉션을 이용해 오브젝트를 만들어준다. 리플렉션은 private으로 선언된 접근 규약을 위반할 수 있는 강력한 기능이 있기 때문이다. 하지만 생성자를 private으로 만들었다는 것은 스태틱 메소드를 통해 오브젝트가 만들어져야 하는 중요한 이유가 있기 때문이므로 이를 무시하고 오브젝트를 강제로 생성하면 위험하다.

일반적으로 private 생성자를 가진 클래스를 빈으로 등록하는 일은 권장되지 않으며 등록하더라도 빈 오브젝트가 바르게 동작하지 않을 가능성이 있으니 주의해야 한다.

p.460)

  • 앞에서 데코레이터 패턴이 적용된 프록시를 사용하면 많은 장점이 있음에도 적극적으로 활용되지 못하는데는 두 가지 문제점이 있다고 설명했다.
    • 첫째는 프록시를 적용할 대상이 구현하고 있는 인터페이스를 구현하는 프록시 클래스를 일일이 만들어야 한다는 번거로움이고,
    • 둘째는 부가적인 기능이 여러 메소드에서 반복적으로 나타나게 돼서 코드의 중복 문제가 발생한다는 점이다.

지금까지 살펴본 프록시 패고리 빈은 이 두가지 문제를 해결해준다.

p.465)

MethodInvocation은 일종의 콜백 오브젝트로, proceed() 메소드를 실행하면 타깃 오브젝트의 메소드를 내부적으로 실행해주는 기능이 있다. 그렇다면 MethodInvocation구현 클래스는 일종의 공유 가능한 템플릿처럼 동작하는 것이다.

바로 이 점이 JDK의 다이내믹 프록시를 직접 사용하는 코드와 스프링이 제공해주는 추상화 기능인 ProxyFactoryBean을 사용하는 코드의 가장 큰 차이점이자 ProxyFactoryBean의 장점이다.

스프링은 단순히 메소드 실행을 가로채는 방식 외에도 부가기능을 추가하는 여러가지 다양한 방법을 제공하고 있다. 이름에서 알 수 있듯이 MethodInterceptor처럼 타깃 오브젝트에 적용하는 부가기능을 담은 오브젝트를 스프링에서는 어드바이스라고 부른다.

스프링의 ProxyFactoryBean은 어떻게 인터페이스 타입을 제공받지도 않고 인터페이스를 구현한 프록시를 만들 수 있을까?

인터페이스 자동검출 기능을 사용해 타깃 오브젝특 구현하고 있는 인터페이스 정보를 알아낸다. 그리고 알아낸 인터페이스를 모두 구현하는 프록시를 만들어준다. 타깃 오브젝트가 구현하고 있는 모든 인터페이스를 동일하게 구현하는 프록시를 만들어주는 기능이다. 타깃 오브젝트가 구현하는 인터페이스 중에서 일부만 프록시에 적용하기를 원한다면 인터페이스를 직접 제공해줘도 된다.

p.467)

물론 프록시의 핵심 가치는 타킷을 대신해서 클라이언트의 요청을 받아 처리하는 오브젝트로서의 존재 자체이므로, 메소드를 선별하는 기능은 프록시로부터 다시 분리하는 편이 낫다. 메소드를 선정하는 일도 일종의 교환 가능한 알고리즘이므로 전략 패턴을 적용할 수 있기 때문이다.

JDK 다이내믹 프록시 vs 스프링의 ProxyBeanFactory

JDK 다이내믹 프록시

JDK 다이내믹 프록시는 부가기능을 가진 InvocationHandler가 타깃과 메소드 선정 알고리즘 코드에 의존한다. 만약 타깃이 다르고 메소드 선정 방식이 다르다면 InvocationHandler 오브젝트를 여러 프록시가 공유할 수 없다. 타깃과 메소드 선정 알고리즘은 DI를 통해 분리할 수는 있지만 한번 빈으로 구성된 InvocationHandler 오브젝트는, 오브젝트 차원에서 특정 타깃을 위한 프록시에 제한된다는 뜻이다.

그래서 InvocationHandler는 굳이 따로 빈으로 등록하는 대신 TxProxyFactoryBean 내부에서 매번 생성하도록 만들었던 것이다. 따라서 타깃 변경과 메소드 선정 알고리즘 변경 같은 확장이 필요하면 팩토리 빈 내의 프록시 생성코드를 직접 변경해야 한다. 결국 확장에는 유연하게 열려있지 못하고 관련 없는 코드의 변경이 필요할 수 있는, OCP 원칙을 깔끔하게 잘 지키지 못하는 어정쩡한 구조라고 볼 수 있다.

스프링의 ProxyBeanFactory

반면 스프링의 ProxyBeanFactory 방식으 두 가지 확장 기능인 부가기능메소드 선정 알고리즘을 활용용하는 유연한 구조를 제공한다.

스프링은 부가기능을 제공하는 오브젝트를 어드바이스라고 부르고, 메소드 선정 알고리즘을 담은 오브젝트를 포인트컷이라고 부른다. 어드바이스와 포인트컷은 모두 프록시에 DI로 주입돼서 사용된다. 두 가지 모두 여러 프록시에서 공유가 가능하도록 만들어지기 때문에 스프링의 싱글톤 빈으로 등록이 가능하다.

프록시는 클라이언트로부터 요청을 받으면서 먼저 포인트컷에게 부가닝을 부여할 메소드인지 확인해달라고 요청한다 포인트컷은 PointCut 인터페이스를 구현해서 만들면 된다. 프록시는 포인트컷으로부터 부가기능을 적용할 대상인지 확인 받으면, MethodInterceptor 타입의 어드바이스를 호출한다. 어드바이스는 JDK의 다이내믹 프록시의 InvocationHnadler와 달리 직접 타깃을 호출하지 않는다. 자신이 공유돼야하므로 타깃 정보라는 상태를 가질 수 없다. 따라서 타깃에 직접 의존하지 않도록 일종의 템플릿 구조로 설계돼어 있다. 어드바이스가 부가기능을 부여하는 중에 타깃 메소드의 호출이 필요하면 프록시로부터 전달받은 MethodInvocation 타입 콜백 오브젝트의 proceed()메소드를 호출해주기만 하면 된다.

p.470)

어드바이저 = 포인트컷(메소드 선정 알고리즘) + 어드바이스(부가기능)

p.475)

스프링의 AOP

투명한 부가기능 형태로 제공돼야 한다. 투명하다는 건 부가기능을 적용한 후에도 기존 설계와 코드에는 영향을 주지 않는다는 뜻이다. 마치 투명한 유리를 사이에 둔 것처럼 다른 코드에서는 그 존재가 보이지 않지만, 메소드가 호출되는 과정에 다이내믹하게 참여해서 부가적인 기능을 제공해주도록 만드는 것이다.