Spring AOP 실습

이정수·2025년 10월 20일

Spring

목록 보기
6/18

AOP 실습

  • Spring AOP, AspectJ Dependency 정의
    gradle 기준 build.gradle에 정의 후 Reload 수행.
implementation 'org.springframework.boot:spring-boot-starter-aop'
  • Spring AOP 실습을 위한 간단한 Business Logic 구현
    레이어드 아키텍처에 따라 각각 Repository 클래스( = Persistence Layer )와 Service 클래스( = Business Layer )를 구분하여 클래스 생성
    고수준 클래스에서 저수준 클래스의존
    Logger 활용
@Service
class BusinessService{
	private BusinessRepository repository;
	public BusinessService(BusinessRepository repository) {
		this.repository = repository;
	}
	public int calMax(){
		int[] data = repository.getData();
		return Arrays.stream(data).max().orElse(0);
	}
}

Business Layer역할의 Business Logic 구현

@Repository
class BusinessRepository{
	public int[] getData(){
		return new int[]{1,2,3,4,5,6,7,8,9,10};
	}
}

Persistence Layer 역할의 Persistence Logic 구현

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Component
public class AOPPractice implements CommandLineRunner {
	Logger logger = LoggerFactory.getLogger(getClass());
	private BusinessService service;
	public AOPPractice(BusinessService service){
		this.service = service;
	}
	@Override
	public void run(String... args) throws IOException {
		logger.info("Max Value is {}", service.calMax());
	}
}

CommandLineRunner구현메소드 run()을 통해 어플리케이션 실행 시 Service에 구현된 Business Logic을 실행

org.slf4j.LoggerLoggerFactory.getLogger(getClass());를 통해 Logger instance 생성 및 logger객체.로깅수준("log message", 값);으로 정의된 로깅수준( info )로 Console에 log를 표현.

Logging 목적의 Aspect 생성하기
Business Layer , Persistence Layer에 동일하게 존재하는 Cross Cutting ConcernLogging 기능을 Aspect로 분리
Aspect는 양쪽 LayerSpring Bean에서 Method를 실행 시 Logging하는 역할을 수행
AOP 용도클래스 생성 시 뒤에 이름에 Aspect 키워드를 기입하는게 관례

  • @Aspect가 선언된 Class 생성 및 내부에 @Before으로 선언된 Advice Method생성.
    Cross Cutting Concern에 해당하는 클래스@Component@Aspect를 선언
    @Component를 선언하여 Spring Bean으로 등록

    @Before를 선언하여 Pointcut으로 지정된 JoinPoint를 Intercept하여 Cross Cutting Concern가 정의된 Advice 메서드가 실행되도록 설정
@Component
@Aspect
public class LoggerAspect {
	private Logger logger = LoggerFactory.getLogger(getClass());
	// com.ktcloud.excercise.AOP 패키지의 하위 Spring Bean의
	// 모든 Method가 호출되기 전에 intercept하여 선언된 Method가 실행.
	@Before("execution(* com.ktcloud.excercise.AOP.*.*(..))")
	// JoinPoint를 통해 인터셉트한 Method의 이름, 매개변수, 반환값등의 정보를 매개변수로 제공.
	public void beforeLogMethodCall(JoinPoint joinPoint) {
		// Logger Logic 구현
		logger.info("beforelogMethodCalled - {}",joinPoint);
		// beforelogMethodCalled - execution(void com.ktcloud.excercise.AOP.AOPPractice.run(String[]))
	}
}

@Aspect를 선언하여 해당 Class가 AOP 기능을 수행하는 Aspect임을 선언 및 @Component을 통해 사전에 Spring Bean으로서 등록

Cross Cutting Concern( = Logger )이 구현된 beforeLogMethodCall()@Before("AspectJ Pointcut표현식")을 선언하여 AspectJ Pointcut 표현식에 정의된 Package경로 상 Spring BeanMethod가 실행되기 전 인터셉트하여 beforeLogMethodCall() Method를 실행.

매개변수JoinPoint 객체를 지정 시 인터셉트되는 JoinPoint이름, 매개변수, 반환값 등의 정보를 확인가능

。 다음 info 수준의 log가 Console에 도출.
@Before에서 설정된 패키지 경로의 범위에 포함되는 Spring BeanMethod가 실행되기전에 beforeLogMethodCall()가 각각 인터셉트하여 실행.

JoinPoint를 통해 인터셉트Method이름, 매개변수, 반환값등의 정보를 제공하여 로그로 출력

AOP AnnotationPointcut 구문을 공통화하여 Package경로가 임의로 변경되더라도 쉽게 Configuration 하도록 설정하기
。기존의 모든 AOP 어노테이션에 정의한 Pointcut특정Package경로를 정의하는 AspectJ Pointcut 표현식을 포함하는 @Pointcut Method의 경로로 통일
Package경로가 변경되더라도 모든 AOP 어노테이션을 수정하는게 아닌, @Pointcut에 정의된 AspectJ Pointcut 표현식을 수정

// 특정 Package경로를 정의하는 AspectJ Pointcut 표현식을 정의하는 @Pointcut Method
	// 해당 @Pointcut Method의 경로를 다른 AOP Method의 pointcut으로 설정하여 공통적으로 해당 Package경로를 참조하도록 설정
	@Pointcut("execution(* com.ktcloud.excercise.AOP.*.*(..))")
	public void DataPackageConfig(){}

▶ 해당 @Pointcut Method의 경로를 다른 AOP Methodpointcut으로 설정하여 공통적으로 해당 Package경로를 참조하도록 설정.

@Component
@Aspect
public class LoggerAspect {
	// 특정 Package경로를 정의하는 AspectJ Pointcut 표현식을 정의하는 @Pointcut Method
	// 해당 @Pointcut Method의 경로를 다른 AOP Method의 pointcut으로 설정하여 공통적으로 해당 Package경로를 참조하도록 설정
	@Pointcut("execution(* com.ktcloud.excercise.AOP.*.*(..))")
	public void DataPackageConfig(){}
	// Pointcut을 특정Package경로를 정의하는 AspectJ Pointcut 표현식을 정의하는 @Pointcut Method의 경로로 설정.
	private Logger logger = LoggerFactory.getLogger(getClass());
	@Before("DataPackageConfig()")
	public void beforeLogMethodCall(JoinPoint joinPoint) {
		logger.info("beforelogMethodCalled - {}",joinPoint);
	}
	@Around("DataPackageConfig()")
	public Object findExecutionTime(ProceedingJoinPoint proccedingjoinPoint) throws Throwable {
		long startTimeMilis = System.currentTimeMillis();
		Object returnValue = proccedingjoinPoint.proceed();
		long endTimeMilis = System.currentTimeMillis();
		long durationTimeMilis = endTimeMilis - startTimeMilis;
		logger.info("@Around 적용 Aspect - {} Method 실행시간 {} ms", proccedingjoinPoint , durationTimeMilis);
		return returnValue;
	}
}


@Pointcut Method의 경로로 설정하더라도 문제없이 실행됨.

  • 커스텀 Annotation 생성후 해당 Annotation이 선언된 Method 호출 전에 @Before로 Intercept하기.
    JAVA 어노테이션 관련
    @Retention(RetentionPolicy.RUNTIME) :
    。선언된 커스텀 Annotation이 Compile 뿐만아닌 Runtime에도 유지되어 java reflection에 의해 접근되도록 설정

    @Target(ElementType.METHOD) :
    。선언된 커스텀 Annotation이 Method에서만 선언되도록 적용.
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
// 선언된 커스텀 Annotation이 Method에서만 선언되도록 적용.
@Target(ElementType.METHOD)
// 선언된 커스텀 Annotation이 Compile 뿐만아닌 Runtime에도 유지되어 java reflection에 의해 접근되도록 설정
@Retention(RetentionPolicy.RUNTIME)
public @interface Tracktime {}
    @Pointcut("@annotation(com.wjdtn747.rest.webservices.aop_practice.aspects.Tracktime)")
    public void TracktimePointcut(){}

▶ 해당 AnnotationPointcut으로 활용 가능. AOP 관련

          // @Pointcut Method 경로를 pointcut으로 설정하여 해당 Annotation에 접근 
@Before("com.wjdtn747.rest.webservices.aop_practice.aspects.CommonPointcutConfig.TracktimePointcut()")
    public void logMethodCall(JoinPoint joinPoint){
        // Advice 부분
        logger.info("logMethodCalled - {}",joinPoint);
    }

▶ 이후 해당 Custom Annotation이 선언된 Method가 호출되기전에 이를 Pointcut에 의해 intercept하여 해당 @Before(AspectJ Pointcut)이 선언된 Method를 실행.

profile
공부기록 블로그

0개의 댓글