티스토리 뷰

study/Spring

chap 07 - AOP 프로그래밍

xoxowo 2023. 1. 10. 20:58

초보 웹 개발자를 위한 스프링 5 프로그램 입문을 보고 복습 겸 개인 학습 정리입니다. (windows 기준)

 

 

 

AOP 

Aspect Oriented Programming의 약자 (관점 지향 프로그래밍)

여러 객체에 공통으로 적용할 수 있는 기능을 분리해서 재사용성을 높여주는 프로그래밍 기법

핵심 기능과 공통 기능의 구현을 분리하여 핵심 기능을 구현한 코드의 수정 없이 공통 기능을 적용할 수 있게 만들어 준다.

 

기본 개념은  핵심 기능에 공통 기능을 삽입하는 것.

핵심 기능의 코드를 수정하지 않으면서 공통 기능의 구현을 추가하는 것이 AOP

공통 기능을 Aspect라고 함

 

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

 

3가지 방법

- 컴파일 시점에 코드에 공통 기능을 삽입하는 법

- 클래스 로딩 시점에 공통 기능을 삽입하는 법

- 런타임에 프록시 객체를 생성해서 공통 기능을 삽입하는 법 (스프링이 제공하는 방법)

 

AOP 주요 용어

용어 의미
Advice 언제 공통 관심 기능을 핵심 로직에 적용할 지 정의
Joinpoint Advice를 적용 가능한 지점을 의미 메서드 호출, 필드 값 변경 등이 Joinpoint에 해당
Pointcut Joinpoint의 부분 집합 실제 Advice가 적용되는 Joinpoint를 나타낸다
Weaving Advice를 핵심 로직 코드에 적용하는 것을 Weaving이라 함
Aspect 여러 객체에 공통으로 적용되는 기능을 의미, 트랜젝션이나 보안 등이 좋은 예

 

Advice 종류

스프링은 프록시를 이용하여 메서드 호출 시점에 Aspect를 적용하기 때문에 구현 가능한 Advice 종류는 아래와 같다.

Before Advice, After Advice 등 이있지만 보통 Around Advice를 사용한다.

 

AroundAdvice - 익셉션 발생 여부와 상관 없이 대상 객체의 메서드 실행 후 공통 기능 실행

 

 

 

스프링 AOP구현

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

 

 

스프링 AOP를 이용하여 공통 기능을 구현하고 적용하는 방법 

 

- Aspect로 사용할 클래스에 @Aspect 애노테이션 붙이기

 - @Pointcut 애노테이션으로 공통 기능을 적용할 Pointcut 정의 execution 명시자로 범위 설정

- 공통 기능을 구현한 메서드에 @Around 애노테이션 적용 

※ @Around에도 execution 명시자를 적용할 수 있다.

 

예제 프로젝트 만들기 (메이븐 기준)  예제 코드

sp5-chap07 프로젝트 폴더 생성

프로젝트(sp5-chap07) 하위 폴더로 src/main/java 생성

sp5-chap07 폴더 내부에 pom.xml 생성

+ pom.xml 파일에 aspectjweaver dependency 추가

이클립스에서 sp5-chap07 폴더에 생성한 메이븐 프로젝트 import

 

 sp5-chap07/src/main/java/config/AppCtx.java

package aspect;

import java.util.Arrays;

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

@Aspect // 스프링 AOP 적용할 클래스에 붙이기
public class ExeTimeAspect {

        @Pointcut("execution(public * chap07..*(..))") // 공통 기능을 적용할 대상 설정
        private void publicTarget() {} 
  	// chap07 패키지와 그 하위 패키지에 위치한 public 메서드를 적용할 대상으로 설정했다는 의미
  
        @Around("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));
            }
        }
}

 

sp5-chap07/src/main/java/main/MainAspect.java

package main;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import chap07.Calculator;
import config.AppCtx;

public class MainAspect {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext ctx = 
        new AnnotationConfigApplicationContext(AppCtx.class);
    
        Calculator cal = ctx.getBean("calculator", Calculator.class);
        long fiveFact = cal.factorial(5);
        System.out.println("cal.factorial(5) = "+ fiveFact);
        System.out.println(cal.getClass().getName());
        ctx.close();
    }
}

 

[Run As] → [Java Application] 누르면 아래와 같이 출력되는데

 

 

 

 

RecCalculator.factorial([5]) 실행 시간 : 1683300 ns 

→ ExeTimeAspect 클래스의 measure() 메서드가 출력

 

cal.factorial(5) = 120 

→ MainAspect 클래스의 System.out.println("cal.factorial(5) = "+ fiveFact); 출력


jdk.proxy2.$Proxy18 

→ MainAspect 클래스의 System.out.println(cal.getClass().getName()); 출력

(스프링이 런타임에 생성한 프록시 객체의 클래스 이름)

 

 

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

 

빈 객체가 인터페이스를 상속할 때 클래스를 이용해서 프록시를 생성하고자 할 때

@EnableAspectJAutoProxy의 proxyTargetClass 속성을 true로 지정하면 자바 클래스를 상속받아 프록시를 생성한다.

@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class AppCtx {...
	.. 생략 ..
}

 

execution 명시자

 

기본형식

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

 

수식어 패턴은 생략이 가능 public, protected 

※ 스프링 AOPsms public 메서드만 적용할 수 있다.

 

리턴 타입 패턴은 리턴 타입을 명시

클래스 이름 패턴? 메서드 이름 패턴은 클래스 이름 및 메서드 이름을 패턴으로 명시

파라미터패턴은 매칭될 파라미터에 대해 명시

 

각 패턴은 ' * '을 이용하여 모든 값을 표현

'..'(점 두 개)를 이용하여 0개 이상이라는 의미를 표현할 수 있다.

 

예시

설명
execution(public void set*(..)) 리턴 타입 void
메서드 이름 set으로 시작
파라미터가 0개 이상인 메서드 호출
execution(*chap07.*.*()) chap07 패키지 타입에 속한
파라미터가 없는 모든 메서드 호출
execution(*chap07..*.*(..)) chap07 패키지 및 하위 패키지에 있는
파라미터가 0개 이상인 메서드 호출
execution(Long chap07.Calculator.factorial(..)) 리턴타입이 Long
Calculator 타입의
factorial() 메서드 호출
execution(* get*(*)) 이름이 get으로 시작
파라미터가 한 개인 메서드 호출
execution(* get*(*, *)) 이름이 get으로 시작
파라미터가 두 개인 메서드 호출
execution( * read*(lnteger, ..)) 메서드 이름이 read로 시작
첫번째 파라미터 타입이 Integer이며 (int)
한 개 이상 파라미터를 갖는 메서드 호출

 

댓글