Aspect 적용

jylee·2024년 1월 23일
0

그냥생각나는거

목록 보기
34/48

Pointcut

package com.example.aop;

import org.aspectj.lang.annotation.Pointcut;

public class Pointcuts {

	// @LogTraceAnnotation 애노테이션에 적용
    @Pointcut("@annotation(com.example.annotations.LogTraceAnnotation)")
    public void logTrace() {
    }

	// exceptClear()를 제외한
    // com.example.app 폴더 하위 모든 메소드에 적용
    @Pointcut("execution(* com.example.app..*(..)) && exceptClear()")
    public void appPackage() {
    }

	// exceptClear()를 제외한
    // com.example.app.service 폴더 하위 모든 메소드에 적용
    @Pointcut("execution(* com.example.app.service..*(..)) && exceptClear()")
    public void allService() {
    }

	// exceptClear()를 제외한
    // com.example.app.controller 폴더 하위 모든 메소드에 적용
    @Pointcut("execution(* com.example.app.controller..*(..)) && exceptClear()")
    public void allController() {
    }

    // com.example.app 폴더 하위 Impl로 끝나는
    // clear라는 메소드명을 가진에 모든 메소드에 적용
    @Pointcut("!execution(* com.example.app..*Impl.clear(..))")
    public void exceptClear() {
    }
}

Aspect

Aspect의 순서를 정하기 위해선 @Order 애노테이션을 적용해야하는데
이것을 어드바이스 단위가 아니라 클래스 단위로 적용할 수 있다
그래서 하나의 애스펙트에 여러 어드바이스가 있으면 순서를 보장 받을 수 없다.
따라서 애스펙트를 별도의 클래스로 분리해야 한다.

package com.example.aop;

import com.example.annotations.RetryAnnotation;
import com.example.aop.trace.TraceStatus;
import com.example.aop.trace.LogTrace;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.core.annotation.Order;

@Slf4j
public class Aspects {

    private Aspects() {}

    @RequiredArgsConstructor
    @Aspect
    @Order(1)
//    @Component
    public static class LogTraceAspect {
        private final LogTrace logTrace;

        @Around("com.example.aop.Pointcuts.logTrace() || com.example.aop.Pointcuts.appPackage()")
        public <T> T trace(ProceedingJoinPoint joinPoint) throws Throwable {
            log.info("Aspects.LogTraceAspect 메소드 시작");
            // 로그 상태 값
            TraceStatus status = null;
            try {
                String message = joinPoint.getSignature().toShortString();
                // 로그 출력 시작
                status = logTrace.start(message);

                // 로직 수행
                // noinspection unchecked
                T result = (T) joinPoint.proceed();

                // 로그 출력 종료
                logTrace.end(status);

                log.info("Aspects.LogTraceAspect 메소드 종료");
                return result;
            } catch (Exception e) {
                // 에러 로그
                logTrace.exception(status, e);
                throw e;
            }
        }
    }

    @Aspect
    @Order(2)
//    @Component
    public static class RetryAspect {

        @Around("@annotation(retryAnnotation)")
        public <T> T retry(ProceedingJoinPoint joinPoint, RetryAnnotation retryAnnotation) throws Throwable {

            log.info("Aspects.RetryAspect 메소드 시작");
            // 실패 시 반복 횟수
            int maxRetryCount = retryAnnotation.value();

            Exception exception = null;
            for (int tryCount = 1; tryCount <= maxRetryCount; tryCount++) {
                try {
                    log.warn("{}/{}", tryCount, maxRetryCount);

                    // 로직 수행
                    // noinspection unchecked
                    log.info("Aspects.RetryAspect 메소드 종료");
                    return (T) joinPoint.proceed();
                } catch (Exception e) {
                    exception = e;
                }
            }

            // exception 이 null 이 아닌지 확인
            assert exception != null;
            throw exception;
        }
    }

    @Aspect
    @Order(3)
//    @Component
    public static class TransactionAspect {
        @Around("com.example.aop.Pointcuts.allService()")
        public <T> T transaction(ProceedingJoinPoint joinPoint) throws Throwable {
            log.info("Aspects.TransactionAspect 메소드 시작");
            try {
                log.info("[트랜잭션 시작] {}", joinPoint.getSignature());

                // 로직 수행
                // noinspection unchecked
                T result = (T) joinPoint.proceed();

                log.info("[트랜잭션 커밋] {}", joinPoint.getSignature());
                return result;
            } catch (Exception e) {
                log.info("[트랜잭션 롤백] {}", joinPoint.getSignature());
                throw e;
            } finally {
                log.info("[리소스 릴리즈] {}", joinPoint.getSignature());
                log.info("Aspects.TransactionAspect 메소드 종료");
            }
        }
    }
}

AspectConfig

package com.example.config;

import com.example.aop.Aspects;
import com.example.aop.Aspects.*;
import com.example.aop.trace.LogTrace;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

//@Configuration
@RequiredArgsConstructor
public class AspectConfig {
    private final LogTrace logTrace;

    @Bean
    public LogTraceAspect logTraceAspect() {
        return new Aspects.LogTraceAspect(logTrace);
    }

    @Bean
    public RetryAspect retryAspect() {
        return new RetryAspect();
    }

    @Bean
    public TransactionAspect transactionAspect() {
        return new TransactionAspect();
    }
}

SpringApplication

package com.example;

import com.example.app.repository.ItemRepository;
import com.example.app.repository.UserRepository;
import com.example.domain.item.Item;
import com.example.domain.user.Grade;
import com.example.domain.user.User;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@Import({AspectConfig.class})
@SpringBootApplication
@RequiredArgsConstructor
public class SampleApplication {
    private final ItemRepository itemRepository;
    private final UserRepository userRepository;

    public static void main(String[] args) {
        SpringApplication.run(SampleApplication.class, args);
    }
}

@SpringBootApplication 에는 @ComponentScan 애노테이션이 포함되어 있으므로
@Configuration@Component 애노테이션을 찾아 스프링 빈을 알아서 등록한다.

따라서 Aspects 파일의 @Component의 주석 해제 시 @ComponentScan으로 Aspect들이 모두 스프링 빈으로 등록되어 AspectConfig가 없어도 된다.

지금과 같이 @Component@Configuration이 주석 처리되어있거나 없는 경우
@Import({AspectConfig.class}) 애노테이션으로 AspectConfig를 설정 정보로 수동 등록하여 포함시키고
AspectConfig는 Aspect들을 스프링 빈으로 수동 등록한다.

profile
ㅎㅇ

0개의 댓글