작심육일러의 스프링 시작하기(6)-1

서은경·2022년 8월 2일
0

Spring

목록 보기
9/43

AOP

Aspect Oriented Programming ✨ 여러 객체에 공통으로 적용할 수 있는 기능을 분리해서 재사용성을 높여주는 프로그래밍 기법이다. 공통 기능 구현과 핵심 기능 구현을 분리하는 것이 AOP의 핵심이다.

AOP의 기본 개념은 핵심 기능에 공통 기능을 삽입하는 것이다. 즉 핵심 기능의 코드를 수정하지 않으면서 공통 기능의 구현을 추가하는 것이 AOP인데 이 방법에는 세 가지가 있다.

  1. 컴파일 시점에 코드에 공통 기능 삽입
    • AOP 개발 도구가 소스 코드를 컴파일 하기 전에 공통 구현 코드를 소스에 삽입하는 방식으로 동작(AspectJ와 같은 AOP 전용 도구 사용해서 적용)
  2. 클래스 로딩 시점에 바이트 코드에 공통 기능 삽입
    • 클래스를 로딩할 때 바이트 코드에 공통 기능을 클래스에 삽입하는 방식으로 동작(AspectJ와 같은 AOP 전용 도구 사용해서 적용)
  3. 런타임에 프록시 객체를 생성해서 공통 기능 삽입
    • 스프링이 제공하는 AOP 방식! 스프링 AOP는 프록시 객체를 자동으로 만들어주므로 공통 기능을 구현한 클래스만 알맞게 구현하면 됨

AOP 주요 용어

  • Advice
    언제 공통 관심 기능을 핵심 로직에 적용할 지를 정의
    예를 들어 '메서드를 호출하기 전'(언제)에 '트랜잭션 시작'(공통 기능) 기능을 적용한다는 것을 정의한다.
  • Joinpoint
    Advice를 적용 가능한 지점을 의미한다. 메서드 호출, 필드 값 변경 등이 Joinpoint에 해당한다. 스프링은 프록시를 이용해서 AOP를 구현하기 때문에 메서드 호출에 대한 JoinPoint만 지원한다.
  • Pointcut
    Joinpoint의 부분 집합으로서 실제 Advice가 적용되는 Joinpoint를 나타낸다. 스프링에서는 정규 표현식이나 AspectJ의 문법을 이용하여 Pointcut을 정의할 수 있다.
  • Weaving
    Advice를 핵심 로직 코드에 적용하는 것을 말한다.
  • Aspect
    여러 객체에 공통으로 적용되는 기능을 말하며 트랜잭션이나 보안 등이 Aspect의 좋은 예이다.

스프링에서 구현 가능한 Advice 종류

  • Before Advice
    대상 객체의 메서드 호출 전에 공통 기능을 실행
  • After Returning Advice
    대상 객체의 메서드가 익셉션 없이 실행된 이후에 공통 기능을 실행
  • After Throwing Advice
    대상 객체의 메서드가 실행하는 도중 익셈션이 발생한 경우에 공통 기능을 실행
  • After Advice
    익셉션 발생 여부에 상관없이 대상 객체의 메서드 실행 후 공통 기능 실행(try-catch-finally의 finally 블록과 비슷)
  • Around Advice
    대상 객체의 메서드 실행 전, 후 또는 익셉션 발생 시점에 공통 기능을 실행하는데 사용

Around Advice 가 가장 널리 사용되며, 대상 객체의 메서드를 실행하기 전/후, 익셉션 발생 시점 등 다양한 시점에 원하는 기능을 삽입할 수 있기 때문이다. 캐시 기능, 성능 모니터링 기능 같은 Aspect를 구현할 때에는 이 방법을 주로 이용한다.

스프링 AOP 구현

package aspect;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;

import java.util.Arrays;

// 공통 기능에 필요한 코드

@Aspect
public class ExeTimeAspect {

    @Pointcut("execution(public * chap07..*(..))")
    // 공통 기능을 적용할 대상 설정 - chap07 패키지와 그 하위 패키지에 위치한 타입의 public 메서드를 Pointcut으로 설정
    private void publicTarget() {
    }

    @Around("publicTarget()")
    // publicTarget() 메서드에 정의한 Pointcut에 공통 기능을 적용
    // 즉 chap07 패키지나 그 하위 패키지에 속한 빈 객체의 public 메서드에 @Around가 붙은 measure() 메서드를 적용
    public Object measure(ProceedingJoinPoint joinPoint) throws Throwable {
        // ProceedingJoinPoint 파라미터는 프록시 대상 객체의 메서드를 호출할 때 사용

        long start = System.nanoTime();
        try {
            Object result = joinPoint.proceed();
            // 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));

            // getSignature() - 호출한 메서드의 시그니처
            // getTarget() - 대상 객체
            // getArgs() - 인자목록
        }
    }
}
package config;

import aspect.ExeTimeAspect;
import chap07.Calculator;
import chap07.RecCalculator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@Configuration
@EnableAspectJAutoProxy
// @Aspect 어노테이션을 붙인 클래스를 공통 기능으로 적용하기 위해 붙여줌
// 이 어노테이션을 추가하면 스프링은 @Aspect 어노테이션이 붙은 빈 객체를 찾아서 빈 객체의 @Pointcut 설정과 @Around 설정 사용
public class AppCtx {

    @Bean
    public ExeTimeAspect exeTimeAspect() {
        return new ExeTimeAspect();
    }

    @Bean
    public Calculator calculator() {
        return new RecCalculator();
    }

}

🙋‍♀️@Enable류 어노테이션이란 ?
💡 스프링은 @EnableAspectJAutoProxy 와 같이 이름이 Enable로 시작하는 다양한 어노테이션을 제공한다. @Enable 로 시작하는 어노테이션은 관련 기능을 적용하는데 필요한 다양한 스프링 설정을 대신 처리한다.

ProceedingJoinPoint 메서드

Around Advice에서 사용할 공통 기능 메서드는 대부분 파라미터로 전달받은 ProceedingJoinPoint의 proceed() 메서드만 호출하면 된다.

ProceedingJoinPoint 인터페이스가 제공하는 메서드

  • Signature getSignature()
    호출되는 메서드에 대한 정보를 구한다.
  • Object getTarget()
    대상 객체를 구한다.
  • Object[] getArgs()
    파라미터 목록을 구한다.

org.aspectj.lang.Signature 인터페이스가 제공하는 메서드(호출되는 메서드의 정보 제공)

  • String getName()
    호출되는 메서드의 이름을 구한다.
  • String toLongString()
    호출되는 메서드를 완전하게 표현한 문장을 구한다.(메서드의 리턴 타입, 파라미터 타입이 모두 표시된다.)
  • String toShortString()
    호출되는 메서드를 축약해서 표현한 문장을 구한다.(기본 구현은 메서드의 이름만을 구한다.)

0개의 댓글