Technical Note/SPRING

1. weaving의 개념 
cross-cutting concern을 구현한 코드를 핵심 로직안에 삽입하는 것 


2. 세가지 Weaving 방법

  • 컴파일시 Weaving : AspectJ에서 사용하는 방식, AOP가 적용된 class파일이 생성
  • 클래스 로딩 시에 Weaving : AOP라이브러리중 JVM이 클래스를 로딩할 때 클래스 정보를 변경할수있는 Agent를 제공
  • 런타임 시에 Weaving : 런타임시에 AOP를 적용할 때는 소스코드나 클래스 정보 자체를 변경하지 않음
    프록시를 이용하여 AOP를 적용, 프록시 기반에서는 메서드가 호출할때에만 Advice를 적용할수있기 때문에 필드값 변경과 같은 Jointpoint에 대해서는 적용할수 없는 한계가 있음




3. Spring 에서의 AOP
스프링은 자체적으로 프록시 기반의 AOP를 지원하고 있다. 따라서, 스프링 AOP는 메소드 호출 Jointpoint만을 지원한다. 필드 값 변경과같은 Jointpoint를 사용하고 싶다면 AspectJ와 같이 풍부한 기능을 지원하는 AOP도구를 사용해야만 한다.

Spring에서 프록시 객체를 생성할떄 대상 객체가 인터페이스를 구현하고 있어야 한다. 
왜냐하면 Spring은 자바 리플랙션 api가 제공하는 java.lang.reflect.Proxy를 이용하여 프록시 객체를 생성하기 때문이다.
대상 객체가 인터페이스를 구현하고 잇지 않다면, spring은 CGLIB를 이용하여 클래스에 대한 프록시 객체를 생성해야 한다.




4. Spring에서의 AOP 실습

실습 과제 : method 단위의 수행시간을 추출 
아래의 3가지 방식으로 weaving 을 구현해 보았다.
1. Spring API를 이용 
2. AspectJ 5에서 정의한 @Aspect 어노테이션을 이용
3. CGLIB 를 이용




5. 구간별 수행시간 추출 기능 요구사항 정리
1. method 단위 수행시간 계산
2. 내부 class 분석기능

  • preparedstatement등의 Jdbc api class에 접근하여 SQL문 추출 기능
  • HttpClient의 수행시간 계산, URL 추출 기능
  • 기타... 고민해야 할 부분

3. Multi thread 로 수행함으로써 동시에 여러 서비스 구간분석




6. 결과

  • Bean으로 만들어진 Class안에 모든 Method의 수행시간은 추출가능




7. 미해결 문제

  • Bean으로 만들어진 Class가 아닌 경우는 구간분석 툴에서 무시됨
  • preparedstatement등 내부에서 사용하는 Class가 설정파일에 정의된 Advice, Pointcut 정보를 가져오지 못함
  • 위의 항목이 해결되면 Multi Thread 방식으로 확장 계획




참고1 - 용어 정리

  • 공통 관심 사항 : cross-cutting concern
  • 핵심 관심 사항 : core concern
  • Aspect : 여러 객체에 공통으로 적용되는 공통 관심 사항을 Aspect라 함.
  • Advice : 언제 공통 관심 기능을 핵심 로직에 적용할지를 정의하고 있음. 예를 들어, '메서드를 호출하기 전에'(언제) 에 '트랜잭션을 시작한다'(공통기능) 기능을 적용한다는것을 정의
  • Joinpoint : Advice를 적용 가능한 지점. 메소드 호출, 필드값 변경 등 Joinpoint에 해당됨
  • Pointcut : Joinpoint의 부분집합으로써 실제로 advice가 적용되는 Joinpoint를 나타냄. 스프링에서는 정규 표현식이나 AspectJ의 문법을 이용하여 Pointcut을 정의




참고2 - AspectJ의 Pointcut 표현식

execution 명시자 (Advice를 적용할 메소드(핵심관심사항을 구현한 메소드)를 지정)

execution([접근자제어패턴] , 리턴타입패턴 [패키지패턴]메서드이름패턴(파라메터패턴)) [ ] 안의 패턴은 생략 가능

  • execution(public void set*(..))
    public에 리턴값이 없으며, 패키지명은 없고, 메서드는 set으로 시작하며 인자값은 0개 이상인 메서드 호출
  • execution(* com.peoplesgroove..())
    리턴타입에 상관없이 com.peoplesgroove패키지의 인자값이 없는 모든 메서드 호출
  • execution(* com.peoplesgroove...(..))
    리턴타입에 상관없이 com.peoplesgroove 패키지 및 하위 패키지에 있는, 인자값이 0개 이상인 메서드 호출
  • execution(Integer com.peoplesgroove.WriteArticleService.write(..))
    리턴 타입이 Integer인 WriteArticleServlce 의 인자값이 0개 이상인 write() 호출
  • execution(* get*(*))
    메서드 이름이 get으로 시작하는 인자값이 1개인 메서드 호출
  • execution(* get*(,))
    메서드 이름이 get으로 시작하는 인자값이 2개인 메서드 호출
  • execution(* get*(Integer, ..))
    메서드 이름이 get으로 시작하고 첫번째 인자값의 데이터타입이 Integer이며, 1개 이상의 인자값을 갖는 메서드 호출
  • execution(* com..*(..)) && @annotation(@annotation)
    @annotation이 있는 모든 메소드 호출
  • execution(* *(..,@annotation (*), ..))
    @annotation을 파라메터로 갖고 있는 모든 메소드 호출|
within 명시자 (특정 패키지나 클래스, 인터페이스 내의 메소드 지정)
  • within(test.bean.BoardService)
    --> BoardService 인터페이스의 모든 메소드
  • within(test.bean.*)
    --> test.bean 패키지 내의 모든 메소드
  • within(test.bean..*)
    --> test.bean 패키지 및 그 하위 패키지 내의 모든 메소드
bean 명시자 (빈 설정파일에 등록된 빈의 이름을 이용하여 메소드 지정)
  • bean(myBean)
    --> 이름이 myBean 인 빈의 메소드
  • bean(*Service)
    --> 이름이 Service로 끝나는 모든 빈의 메소드

레퍼런스