ch07. AOP 프로그래밍

728x90
참고 도서 : 스프링5 프로그래밍 입문 - 최범균 저

ch07. AOP 프로그래밍

[AOP 위한 프로젝트 준비]

-스프링이 AOP 구현 시 사용하는 모듈 aspectjweaver 추가해야 한다.

-메이븐 pom.xml 파일에 aspectjweaver 의존 추가해야 한다,

- aspectjweaver 모듈은 AOP 설정에 필요한 애노테이션 제공하므로 반드시 추가해야 한다.

스프링 프레임워크의 AOP 기능은 spring-aop모듈이 제공하지만,
spring-context모듈을 의존 대상에 추가해놓음녀 sop모듈도 함께 의존대상에 포함되므로
spring-aop 모듈에 대한 의존을 따로 추가하지 않아도 된다.

 

[a! 팩토리얼 재귀함수로 구현] : 실행 시간 구하기

-실행 시간 기본 매커니즘은 메소드의 실행 전, 후에 시간을 구한 뒤, 그 차이를 구하는 것

-, 재귀호출로 구현한 경우 소스 코드 수정 -> 시간이 여러 번 출력됨

public class RecCalculator implements Calculator {
	@Override
	long start = System.currentTimeMillis();
	try {
        	  if (num == 0)
              return 1;
        	  else
              return num * factorial(num - 1); //재귀 호출
	}finally{
	long end = System.currentTimeMillis();
	System.out.printf(“%d), (end-start));
	}
}
Q. 기존 코드 수정하지 않고 코드 중복도 피할 수 있는 방법 ? -> 프록시 객체
public class ExeTimeCalculator implements Calculator { //프록시 객체
	//필드
	private Calculator delegate;
	//생성자
	public ExeTimeCalculator(Calculator delegate) { //Calculator구현 객체를 인수로 줌 
        		this.delegate = delegate;
    	}
	//재정의
	@Override
	public long factorial(long num) {
		long start = System.nanoTime(); //시작 시간 구함
		long result = delegate.factorial(num);// <-인수로 받은 객체 메소드 실행 시간 
		long end = System.nanoTime(); //끝 시간 구함 

		System.out.printf("%s.factorial(%d) 실행 시간 = %d\n",
				delegate.getClass().getSimpleName(),
				num, (end - start)); //시간 차이 구함 
		return result;
	}
}

 

[프록시]: proxy

-핵심 기능 실행은 다른 객체에 위임하고, 대상 객체 모두에 공통 적용 가능한 부가 기능을 제공하는 객체

-실제 핵심 기능 제공 객체 = 대상 객체

-대상 객체들에 공통 적용할 부가 기능 제공 객체 = 프록시

-> 이처럼 공통 기능 구현과 핵심 기능 구현을 분리하는 것이 AOP의 핵심이다.

 

[AOP] : Aspect Oriented Programming

-여러 객체에 공통 적용될 기능을 따로 분리하여 재사용성 높여주는 프로그래밍 기법

-AOP는 핵심 기능과 공통 기능의 구현을 분리함으로써 핵심 기능 구현 코드 수정없이도

공통 기능 적용할 수 있게 만들어 준다.

->스프링도 프록시를 이용해서 AOP를 구현하고 있다. = 스프링 AOP
                         AOP의 기본 = 핵심 기능에 공통 기능 삽입하는 것.

[핵심기능에 공통 기능 삽입 방법 3가지]

 컴파일 시점 -> 소스 코드에 공통 기능 삽입
 틀래스 로딩 시점 -> 바이트 코드에 공통 기능 삽입
 런타임 -> 프록시 객체 생성해서 공통 기능 삽입 <스프링 제공 AOP 방식>

[스프링 AOP]

프록시 객체를 자동으로 만들어준다.

프록시 클래스를 직접 구현할 필요 X

공통 기능 구현한 클래스만 알맞게 구현하면 됨

[AOP 주요 용어]

Advice 언제 공통 관심 기능을 핵심 로직에 적용할 지를 정의
Joinpoint Advice를 적용 가능한 지점을 의미.
스프링은 프록시 이용해서 AOP 구현하므로 메소드 호출에 대한 Joinpoint만 지원
Pointcut 실제 Advice가 적용되는 Jointpoint.
Jointpoint의 부분집합
Weaving Advice를 핵심 로직 코드에 적용하는 것
Aspect 여러 객체에 공통으로 적용되는 기능

 

[Adivice의 종류]

-스프링은 프록시를 이용해서 메서드 호출 시점에 Aspect를 적용하므로

구현 가능한 Adviece 종류는 다음과 같다.

Before Advice 대상 객체 메소드 호출 -> 공통 기능 실행
After Returning Advice 대상 객체 메소드 익셉션 없이 실행된 이후->공통 기능 실행
After Throwing Advice 대상 객체 메소드 실행 도중 익셉션 발생한 경우
-> 공통 기능 실행
After Advice 익셉션 발생 여부와 상관X 대상 객체 메소드 실행 후
-> 공통 기능 실행
Around Advice*** 대상 객체 메소드 실행 /후 또는 익셉션 발생 시점
-> 공통 기능 실행

*** Around Advice 사용 다양한 시점에 원하는 기능 삽입 가능하므로

[스프링 AOP 구현]

<스프링 AOP를 이용해서 공통 기능 구현 -> 적용하는 방법>

 Aspect로 사용할 클래스에 @Aspect 붙임
 @Pointcut 으로 공통 기능 적용 대상(Pointcut) 정의
 공통 기능 구현 메소드에 @Around 붙임

 

  ▶[@Aspect, @Pointcut, @Around 이용한 AOP 구현]

<Around Advice에서 사용할 Aspect 예시 코드>

[= 메소드 실행 전/(Around)에 사용할 공통 기능(Aspect) 예시 코드 ]

@Aspect // Aspect로 사용할 클래스
public class ExeTimeAspect { 
	@Pointcut("execution(public * chap07..*(..))") 
			//chap 07 및 하위 패키지에 위치한 public 메소드들을 공통 기능 적용 대상 선정 
	private void publicTarget() {
	}
	@Around("publicTarget()") //publicTarget()에 정의된 Pointcut(대상)에 공통 기능 메소드 적용한다.
	public Object measure(ProceedingJoinPoint joinPoint) throws Throwable {
		long start = System.nanoTime();
		try {
			Object result = joinPoint.proceed(); //프록시 대상 객체 메소드의 호출
			return result;
		} finally {
			long finish = System.nanoTime();
			Signature sig = joinPoint.getSignature();
			System.out.printf("%s.%s(%s) 실행 시간 : %d ns\n",
joinPoint.getTarget().getClass().getSimpleName(),sig.getName(), Arrays.toString(joinPoint.getArgs()),
						(finish - start));
		}
	}
 }
-@Aspect 적용 클래스는 AdvicePointcut을 함께 제공한다.
-@Pointcut : 공통 기능 적용 대상 설정
          값 = execution 명시자 이용해서 설정
-@Around : AdviceAround로 설정.
          값에는 publicTarget() | , publicTarget()이 정의한 Pointcut에 공통 기능 적용하겠다.
 

 

<스프링 설정 클래스 작성>

-위에서 @Aspect 클래스를 공통 기능으로 적용하려면 설정클래스에 @EnableAspectJAutoProxy붙여야 함

-이 애노테이션 추가 시, 스프링은 @Aspect 붙은 빈 객체 찾아서 빈 객체에 설정되어 있는 @Pointcut 설정과 @Around 설정을 사용한다.

@Configuration
@EnableAspectJAutoProxy // 공통 기능 적용 위해 붙임
public class AppCtx {
	@Bean
	public ExeTimeAspect exeTimeAspect() {
		return new ExeTimeAspect();
	}
	@Bean
	public Calculator calculator() {
		return new RecCalculator();
	}
}

[ProceedingJoinPoint 인터페이스]

-@Around 붙여준 공통 기능 메소드의 파라미터는 ProceedingJoinPoint 타입이다.

-이 타입의 변수로 대상 객체를 받고 (인터페이스) 내부적으로 정의된 proceed() 메소드로 대상 객체의 핵심 메소드를 호출한다.

@Around("publicTarget()")
public Object measure(ProceedingJoinPoint joinPoint) throws Throwable {
Signature getSignature() 호출되는 메소드에 대한 정보를 구한다
Object getTarget() 대상 객체를 구한다
Object getArgs() 파라미터 목록을 구한다

-이 인터페이스는 대상 객체에 대한 추가 정보에 접근할 수 있도록 다음 메소드를 추가적으로 정의해놓고 제공한다.

추가) + Signature 인터페이스다음 메소드제공.

-String getName(): 호출 메소드 이름

-String toLongString():호출 메소드를 완전히 표현한 문장
(패키지/리턴타입/파라미터 타입 등)

-String toShortString():호출 메소드 축약해서 표현한 문장

 

[프록시 생성 방식]

-스프링은 AOP를 위한 프록시 객체를 생성할 때 실제 생성할 빈 객체가 인터페이스를 상속하면

                 ① 인터페이스를 이용해서 프록시를 생성함

                 ② (인터페이스가 아닌) 클래스를 이용해서 프록시를 생성

            @EnableAspectAoutoProxy(proxyTargetClass = true)
           -proxyTargetClass 속성을 true로 지정 시, 자바 클래스를 상속받아 프록시르 생성

 

[excution 명시자 표현식]

-execution 명시자는 Advice 적용할 메소드 지정 시 사용.

                         ex. @Pointcut(“excution(public * chap07 ..*(..))”)

execution( 수식어패턴/리턴타입/클래스이름/메소드이름(파라미터) )

-스프링 AOPpublic 메소드에만 적용 O / 따라서 수식어 패턴 생략 가능

            * 이용: 모든 값 표현
            ..( 2) 이용 : 0개 이상을 표현

 

[Advice 적용 순서]

-한 개의 Pointcut에 여러 Advice를 적용할 수도 있다.

-어떤 @Aspect가 먼저 적용될 지는 자바 버전, 스프링 프레임워크에 따라 천차만별

Q. 적용 순서가 중요하다면?-> @Order 이용해서 직접 순서 지정해야 한다.
@Order() : 값 작은 대상을 먼저 적용함

 

[@AroundPointcut 설정과 @Pointcut 재사용]

    ⓵ @Around(@Pointcut이 아닌) execution명시자를 직접 지정할 수도 O

    ⓶공통 Pointcut 재사용할 수 O

- 같은 Pointcut을 여러 Advice가 함께 사용 시 사용함

    ⓷별도의 클래스로 Pointcut 정의하여 사용 O

-이때, 별도의 클래스는 빈으로 꼭 등록할 필요는 X

-여러 Aspect에서 공통으로 사용하는 Pointcut이 있다면 별도 클래스에 Pointcut 정의 후,

                                                                                      각 Aspect가 해당 객체 생성하여 사용하도록 구성

 

728x90