(스프링) 4장. 애스펙트 지향 프로그래밍

1. AOP란 무엇인가?

애스펙트의 주목적은 횡단 관심사의 모듈화이다.
횡단관심사란 한마디로 애플리케이션의 여러부분에 영향을 주는 것이다. 일례로, 보안은 하나의 애플리케이션 내에서도 여러 객체 메소드의 보안 규칙에 영향을 미치는 횡단관심사이다.

공통기능을 재사용하기 위해 일반적으로 사용하는 객체지향 기법은 상속이나 위임이다. 하지만 상속은 객체의 정적 구조에 의존하므로 복잡하고 깨지기 쉬운 구조가 되기 십상이며, 위임은 대상 객체에 대한 복잡한 호출로 인해 번거롭다.

애스페그는 대부분 상속이나 위임보다 더 깔끔한 해결책을 제공한다. AOP도 공통기능을 한곳에 정의 하는 것은 기본적으로 동일하다. AOP에서는 횡단 관심사를 애스펙트라는 특별한 클래스로 모듈화한다.

AOP가 다른 기법과 차별화되는 장점은 두가지다.
첫 째, 전체 코드 기반에 흩어져있는 관심사항이 하나의 장소로 응집된다는 점이고,
둘 째, 여타 서비스 모듈이 자신의 주요관심사항(또는 핵심기능)에 대한 코드만 포함하고 그 외 관심사항은 모두 애스펙트로 옮겨지므로 코드가 깔끔해진다는 것이다.

2. AOP용어 정의


  • 어드바이스: 검침원이 주택을 방문하는 이유는 전력량계를 읽어서 전력회사에 보고하기 위해서이다. 그들이 주택목록과 보고하는 정보도 중요하지만, 가장 중요한 작업은 전력사용량을 읽고 기록하는 행위 그 자체이다.
     어드바이스는 애스펙트가 '무엇''언제'할지를 정의한다.

  1. 이전(before)- 어드바이스 대상 메소드가 호출되기 전에 어드바이스 기능을 수행한다.
  2. 이후(after)-결과에 상관없이 어드바이스 대상 메소드가 완료된 후에 어드바이스 기능을 수행한다.
  3. 반환이후(after-returning)- 어드바이스 대상 메소드가 성공적으로 완료된 후에 어드바이스 기능을 수행한다.
  4. 예외발생이후(after-throwing)-어드바이스 대상 메소드가 예외를 던진 후에 어드바이스 기능을 수행한다.
  5. 주위(around)-어드바이스가 어드바이스 대상메소드를 감싸서 어드바이스 대상 메소드 호출 전과 후에 몇 가지 기능을 제공한다.

  • 조인 포인트: 검침원이 전력량계를 읽을 수도 있지만, 그 집의 온도계나 습도계를 읽을 수 있다. 마찬가지로 애플리케이션 하나에도 어드바이스를 적용할 수 있는 곳은 무수히 많다. 이와같이 어드바이스를 적용할 수 있는 곳을 조인포인트(join point)라고 한다. 이렇게 끼워넣을 수 있는 지점으로는 메소드 호출 지점이나 예외발생, 필드 값 수정등이 있다. 이러한 지점에 애스펙트 코드가 삽입되면 애플리케이션의 정상 흐름에 추가작업을 더할 수 있다.
  • 포인트커트: 검침원 한명이 전력회사가 서비스하는 모든 주택을 다 돌아다닐 수 없다.대신 검침원들은 자신이 맡은 지역 내의 주택을 방문한다. 마찬가지로 한 애스펙트가 전체 애플리케이션의 모든 조인 포인트를 다 어드바이스 할 수는 없는 노릇이다. 포인트 커트(pointcut)는 바로 애스펙트가 어드바이스할 조인 포인트의 영역을 좁히는 일을 한다. 포인트커트는 '어디서' 할지를 정의한다. 각 포인트커트는 어드바이스가 위빙(weaving)되어야 하는 하나 이상의 조인 포인트를 정의한다. 간단하게 포인트커드를 지정하는 방법은 클래스나 메소드명을 직접 사용하는 것이지만, 매칭 패턴을 나타내는 정규표현식을 정의하는 방법도 있다. 몇몇AOP프레임워크는 동적 포인트커트를 사용하여, 메소드 파라미터 값처럼 실행 중(runtime)에 얻을 수 있는 정보를 이용해서 어드바이스의 적용여부를 결정한다.
  • 애스펙트: 애스펙트(aspect)는 어드바이스와 포인트커트를 합친(merge)것이다. 두가지 정보가 합쳐지면 애스펙트가 무엇을 언제 어디서 할지, 즉 애스펙트가 필요한 모든 정보가 정의된다.
  • 인트로덕션: 기존 클래스에 변경 없이도 새 메소드나 멤버변수를 추가하는 기능이다.  예를들어, 어떤 객체의 상태가 언제 마지막으로변경되었다는 정보를 추가하는 경우를 생각해보자. 각 클래스마사 해당 정보를 저장하는 대신, Auditable 어드바이스 클래스를 생성하여 setLastModified(Date)메소드와 이 상태를 저장하는 멤버변수를 정의한다. 그다음 인트로덕션을 적용하면 기존 클래스에 소스 코드변경없이 새로운 메소드와 멤버변수를 도입한다.
  • 위빙(weaving): 타깃객체에 애스펙트를 적용해서 새로운 프록시 객체를 생성하는 절차다. 애스펙트는 타깃객체의 조인 포인트로 위빙된다. 위빙은 대상 객체의 생에중 다음과 같은 몇가지 시점에서 수행된다.
  1. 컴파일시간- 타깃 클래스가 컴파일 될떄 애스펙트가 위빙되며, 별도의 컴파일러가 필요하다. AspectJ의 위빙 컴파일러는 이러한 목저으로 사용된다.
  2. 클래스 로드시간(classload time)- 클래스가 JVM에 로드 될 때, 애스펙트가 위빙된다. 애플리케이션에 사용되기 전에 타깃 클래스의 바이트 코드를 인핸스(enhance)하는 특별한 ClassLoader가 필요하다. AspectJ 5의 로드시간 위빙(LTW)기능을 사용하면 클래스로드 시간에 위빙된다.
  3. 실행시간(Runtime)- 애플리케이션 실행 중에 액스펙특 위빙된다. 보통 타깃 객체에 호출을 위임하는 구조의 프록시 객체를 위빙 중에 AOP컨테이너가 동적으로 만들어 낸다. 스프링 AOP애스펙트가 위빙되는 방식이다.

<스프링 AOP 프레임워크의 핵심사항>

1. 스프링 어드바이스는 자바로 작성
--모든 어드바이스는 표준 자바 클래스로 작성되며, 포인트 커트의 경우 어드바이스를 적용할 곳을 정의하는데, 이는 보통 스프링 XML설정 파일에 정의하게 된다.

2. 실행시간에 만드는 스프링 어드바이스

--스프링에서는 빈을 감싸는 프록시 객체를 실행시간에 생성함으로써 애스펙트가 스프링 관리 빈에 위빙된다. 그림에 나와있듯이 프록시 객체는 타깃 객체로 위장해서 어드바이스 대상 메소드의 호출을 가로채고, 타깃 객체로 호출을 전달(forward)한다. 애스펙트의 로직은 프록시가 메소드 호출을 가로채고 나서 실행되며, 그다음에 타깃 빈의 메소드가 호출된다.


3. 스프링은 메소드 조인 포인트만 지원

--각 AOP프레임워크는 서로 다른 조인포인트 모델을 지원한다. 스프링의 경우에는 동적 프록시를 기반으로 AOP를 구현하므로 메소드 조인 포인트만 지원한다. 스프링에서 필드를 포인트 커트로 지정할 수 없다는 사실은, 객체 필드 값의 수정등의 세밀한 제어가 불가능 하다는 의미다. 또한 생성자 포인트 커트를 지정할 수 없으므로 객체를 인스턴스화 할 때도 별다른 어드바이스를 적용할 방도가 없게 된다.
세밀한 제어가 필요하다면 그떄는 AspectJ를 사용한 스프링 AOP를 이용하여 보충하면 된다.



<<포인트 커트를 이용한 조인포인트 선택>>

스프링 AOP와 관련된 AspectJ 포인트 커트에 대해 알아야 하는 가장 중요한 사항은, 스프링은 AspectJ에서 사용할 수 있는 포인트 커트 지정자에 속하는 것만 지원한다는 사실이다.

**스프링은 스프링 애스펙트를 정의하기 위해 AspectJ의 포인트 커트 표현식 언어를 사용한다.

Aspect 지정자     설명

args() : 인자가 주어진 타입의 인스턴스인 조인포인트 매칭을 정의한다.
@args(): 전달된 인자의 런타임 타입이 주어진 타입의 애너테이션을 갖는 조인 포인트 매칭을 정의한다.
execution() : 메소드 실행 조인포인트와 일치시키는 데 사용된다.
this() : AOP프록시의 빈 레퍼런스가 주어진 타입의 인스턴스를 갖는 조인 포인트를 정의한다.
target() : 대상 객체가 주어진 타입을 갖는 조인 포인트를 정의한다.
@target() :  수행중인 객체의 클래스가 주어진 타입의 애너테이션을 갖는 조인포인트를 정의한다.
within() :  특정 타입에 속하는 조인포인트를 정의한다.
@within() : 주어진 애너테이션을 갖는 타입 내 조인포인트를 정의한다.(스프링 AOP를 사용할 때 주어진 애너테이션을 사용하는 타입으로 선언된 메소드 실행)

@annotation :  조인 포인트의 대상객체가 주어진 애너테이션을 갖는 조인포인트를 정의한다.

<<포인트 커트 작성>>

예)

package concert;

public interface Performance{
  public void perform();
}


--> execution(* concert.Performace.perform(..))
-->메소드 실행시작(모든타입리턴, 메소드가 속하는 타입,메소드(모든인자받기))

**포인트 커트범위를 concert패키지로만 제한해야 한다고 가정해보자 이경우 within()지정자를 이용한다.

execution(* concert.Performace.perform(..))&&within(concert.*))



<포인트 커트에서 빈 선택하기>

포인트커트 표현식 내에서 ID로 빈을 지정할 수 있다. bean()은 인자로 빈 ID나 이름을 받고 특정 빈에 대한 포인트커트의 영향을 제한한다.

execution(* concert.Performance.perform())and bean('woodstock')


<<<<<<<<애스펙트 애너테이션 만들기>>>>>>>>>>>

1. 애스펙트 정의하기

공연은 관객없이는 진짜 공연이 될 수 없다. 공연 관점에서 생각해 볼 떄, 관객은 중요하지만 공연 그 자체의 핵심기능은 아니며, 별도의 사항이다.따라서 관객을 공연에 적용할 수 있는 애스펙트로 정의한다.




이 Audience 클래스는 @Aspect라는 애너테이션이 붙어있다. Audience 클래스는 특정 애스펙트를 정의하기 위해 애너테이션 되는 메소드다.


**** 스프링은 어드바이스 메소드를 선언하기 위해 AspectJ 애너테이션을 사용한다****

@After : 어드바이스 메소드는 어드바이스된 메소드가 반환되거나 예외 상황이 발생한 이후에 호출된다.
@AfterReturning :  어드바이스 메소드는 어드바이스된 메소드가 반환된 이후에 호출된다.
@AfterThrowing : 어드바이스 메소드는 어드바이스된 메소드가 예외 상황을 발생시킨 이후에 호출된다.
@Around : 어드바이스 메소드는 어드바이스된 메소드를 감싼다.



**@Pointcut 애너테이션은 @AspectJ애스펙트에서 재사용 가능한 포인트 커트를 정의한다.



여기서 멈춘다면 Audience는 스프링 컨테이너의 빈일 뿐읻. 비록 AspectJ 애너테이션을 사용하더라도 애스펙트로 변경하는 프록시를 생성하고, 애너테이션을 해석하는 무엇인가 없이는 애스펙트로 취급되지 않는다.

Javaconfig를 사용한다면 설정 클래스의 클래스 레벨에서 @EnableAspectJAutoProxy애너테이션을 적용하여 오토-프록싱(auto-proxing)을 사용한다.


그러나 스프링에서 빈을 와이어링 하기 위해서 XML을 사용한다면, 스프링의 aop네임스페이스에서 <aop:aspectj-autoproxy> 요소를 사용해야 한다.

댓글

이 블로그의 인기 게시물

(18장) WebSocekt과 STOMP를 사용하여 메시징하기

(네트워크)폴링방식 vs 롱 폴링방식

(ElasticSearch) 결과에서 순서 정렬