04장 AOP(관점 지향 프로그래밍)

박준우·2025년 7월 2일
0

Spring Boot

목록 보기
4/14

1. AOP의 기초지식

AOP(Aspect Oriented Programing:관점 지향 프로그래밍)이란?

프로그램을 중단점 관심사(핵심 기능 구현 코드)와 횡단적 관심사(예외처리, DB트랜잭션 제어, 로그 정보 출력 등 부수적 기능)을 구분하여, 횡단적 관심사를 여러 메소드 안에 넣지 않고, 코드를 재활용하기 위해 사용하는 프로그래밍 기법이다.

예를 들어 예외 처리는 모든 메소드에서 길면서, 동일한 내용의 코드를 반복해서 사용해야하는 단점이 있다. 따라서 횡단적 관심사를 분리하여 프로그래밍하는 Spring boot의 기능을 AOP라고 한다.

AOP 프로그래밍을 위한 고유 용어

용어내용
Advice특정 타이밍에 실행되도록 설정한 횡단적 관심사를 구현한 코드
Join PointAdvice가 실행되는 장소
Point cut어떤 Join Point(위치)에서 advice(코드)가 실행될지 지정하는 조건
AspectAdvice와 Pointcut을 결합한 클래스로, 횡단적 관심사를 어디서 어떻게 실행할지 정의
Interceptor함수호출을 포착하고, 그 전후에 어떤 처리를 하는 객체
TargetAspect가 적용되는 대상으로, 중심적 관심사 이다.

AOP의 구조

예를 들어
A클래스[B.X메서드 호출]
B클래스[X메서드(중단적관심사, 횡단적관심사)]

이러한 구성의 클래스 존재시, 아래처럼 횡단적 관심사를 분리하는 것이 가능하다.

A클래스[B.X메서드 호출]
B클래스[X메서드(중단적관심사)]
Aspect클래스[Advice(횡단적 관심사)]

이 때, A클래스에서 B클래스를 호출할 때, 내부적으로 AOP Proxy가 먼저 명령을 받고, B.X 의 중심적 관심사와 Aspect 클래스의 Advice를 호출하는 방식으로 Spring boot는 제어한다.

Advice의 종류와 설명

Advice는 횡단적 관심사의 구현 코드이다. 어떤 타이밍에 코드를 실행할지 지정할 수 있다.

애너테이션내용사용 예시
@Before중심적 관심사 시작 전에 횡단적관심사 수행로그인확인, 권한 확인
@AfterReturning중심적 관심사가 성공적으로 완료된 후 횡단적관심사 수행DB 트랜잭션 커밋, 성공 메시지 표시
@AlterThrowing중심적관심사에서 예외 발생 시 추가 처리내용 수행오류 로그 기록, 오류메시지 표기
@After중심적관심사 실행 후(성공, 실패 상관없이) 수행리소스헤제, 후처리
@Around중심적관심사 전 후로 수행처리시간 측정, 트랜잭션 제어

Point cut 식

포인트 컷은 어떤 위치에서 Advice가 실행될지 AOP proxy에게 알려주는 역할을 한다.

execution 사용법

포인트 컷 설정 방법중 하나인 execution 사용법에 대해 알아보자.

execution(반환 패키지 타입.클래스명.메소드명(인수))

여기에 더해 아래의 와일드 카드(*, .., +)를 사용하여, 적용범위를 설정할 수 있다.

execution 사용 예시

예시내용
execution(* com.example.service.DemoService.*(..))Demoservice 클래스의 메소드 위치에 advice를 선언
execution(* com.example.service.DemoService.select*(..))Demoservice 클래스 select로 시작하는 메소드에 advice를 선언
execution(String com.example.service.DemoService.*(..))DeomService클래스 반환값이 String타입인 메소드에 advice를 선언
execution(* com.example.service.DemoService.*(String, ..))DeomService클래스의 첫 인수가 String타입인 메서드에 advice를 선언
execution(* com.example.service. *.* (..))지정된 패키지 아래 모든 클래스의 메서드에 advice를 선언
execution(* com.example.service. *..* (..))지정된 패키지와 하위 패키지 아래 모든 클래스의 메서드에 advice를 선언
execution(* com.example.service.DemoService.* (*))클래스의 인수가 1개인 메서드에 advice를 선언

2. AOP프로그램 만들기

0. 사전 프로그램 세팅

의존성 추가
1. Spring Boot DevTools

AOP 사용 세팅
AOP를 사용하기 위해서 build.gradle 파일, dependencies에 아래 코드를 추가한다.

implementation 'org.springframework.boot:spring-boot-starter-aop'

빌드 세팅

  1. 설정-빌드도구-gradle-gradle jvm에서 java버전 변경하기.
  2. 프로젝트 구조- 프로젝트설정-프로젝트SDK에서 java버전 변경하기.

1. target만들기

target이란? Aspect가 적용되는 대상으로, 중심적 관심사이다.

@Service
public class TargetService {
    public void sayHello(String name){
        System.out.println("안녕," + name + "!");
    }

    public void sayGoodbye(String name){
        System.out.println("잘가," + name + "!");
    }
}

해설

@Service: 이 클래스는 앞으로 Bean이 자동으로 객체를 생성해준다.

2. Aspect만들기

Aspect란? Advice(횡단적 관심사 구현코드)메소드들을 저장하는 클래스이다.

@Aspect
@Component
public class LoggingAspect {
    // @Before("execution(* com.example.demo.service.TargetService.*(..))")
    public void beforeAdvice(JoinPoint joinPoint) {
        LocalDateTime startTime = LocalDateTime.now(); // 현재 날짜 및 시간 가져오기
        String formattedTime = startTime.format(DateTimeFormatter.ofPattern("HH:mm:ss:SSS:SSS"));
        System.out.println("-------【@Before】-------");
        System.out.println("Before method:" + joinPoint.getSignature());
        System.out.println("메서드 시작: " + formattedTime);
    }

#### 해설
1. @Aspect: 이 클래스가 Advice를 구현하는 클래스임을 지정한다. 
2. @Component: Di컨테이너가 이 객체를 생성하도록 지정
3. @Before("execution(* com.example.demo.service.TargetService.\*(..))"): 
@Before: 지정된 포인트컷과 일치하는 메소드 호출 전에 이 advice메소드가 실행 된다.
TargetService.*(..)  : TargetService클래스의 (.*) 모든 메서드 (..) 아무 타입, 인자 상관 없이.
4. JoinPoint 객체: advice가 적용되는 시점(=조인포인트) 정보를 가지는 객체이다.
   JoinPoint 객체는 Advice(@Before)가 실행될 때 DI가 자동으로 인자를 넘겨준다. 
5. JoinPoint.getSignature() : 이 메소드의 이름과, 리턴타입 등 메소드 정보를 넘겨준다. 

즉, 이 Advice는 TargetService의 모든 메소드가 실행되기 전에 DI가 JoinPoint 객체를 받아와 화면에 
메소드이름, 리턴타입등 메소드정보를 화면상에 출력하고, advice가 적용되는 시점도 출력하는 역할을 한다
볼 수 있다. 

    // @After("execution(* com.example.demo.service.TargetService.*(..))")
    public void afterAdvice(JoinPoint joinPoint) {
        LocalDateTime endTime = LocalDateTime.now(); // 현재 날짜 및 시간 가져오기
        String formattedTime = endTime.format(DateTimeFormatter.ofPattern("HH:mm:ss:SSS"));
        System.out.println("-------【@After 】-------");
        System.out.println("After method:" + joinPoint.getSignature());
        System.out.println("메서드 종료: " + formattedTime);
    }

#### 해설
1. @After("execution(* com.example.demo.service.TargetService.*(..))"):
@After: 지정된 포인트컷과 일치하는 메소드 호출 후에 이 advice메소드가 실행 된다.
TargetService.*(..)  : TargetService클래스의 (.*) 모든 메서드 (..) 아무 타입, 인자 상관 없이.
2. 이하 동일

    // @Around("execution(* com.example.demo.service.TargetService.*(..))")
    public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
        long startTime = System.currentTimeMillis();
        System.out.println("=====【@Around:전】=====");
        System.out.println("■Target");
        System.out.println(" 클래스:" + joinPoint.getSignature().getDeclaringTypeName());
        System.out.println(" 메서드:" + joinPoint.getSignature().getName());

        Object result = joinPoint.proceed(); // 실행 메서드 호출

        System.out.println("=====【@Around:후】=====");
        long elapsedTime = System.currentTimeMillis() - startTime;
        System.out.println("메서드 실행 시간: " + elapsedTime + " milliseconds.");
        return result; //result 메모리 반환
    }
#### 해설
1. @Around("execution(* com.example.demo.service.TargetService.*(..))"):
@Around: 지정된 포인트컷과 일치하는 메소드 호출 전+후에 이 advice메소드가 실행 된다. 
TargetService.*(..)  : TargetService클래스의 (.*) 모든 메서드 (..) 아무 타입, 인자 상관 없이.
2. joinPoint.proceed() : 이 함수를 통해 실제로 타깃(중심적 관심사) 메소드를 실행할 수 있다. 

}

3. 시작 클래스 생성하기

@SpringBootApplication
public class AopSampleApplication {

	public static void main(String[] args) {
		SpringApplication.run(AopSampleApplication.class, args)
				.getBean(AopSampleApplication.class).exe();
	} // 메인함수에서 DI의 Bean을 가져와서 exe메소드를 실행한다. 

	@Autowired
	private TargetService service;

	/** 실행 */
	private void exe() {
		service.sayHello("철수");
		// 알기 쉽게 구분선을 표시
		System.out.println("■□■□■□■□■□");
		service.sayGoodbye("영희");
	}
}
#### 해석
1. @SpringBootApplication: 이 코드가 Spring boot라는 것을 알려주면, 기본환경세팅된다.
2. @Autowired : service필드에 TargetService의 Bean이 DI로부터 자동 삽입된다.

#### 총정리
메인함수와 exe를 통해 TargetService 클래스 service객체의 함수들을 실행하면, 
이를 advice(횡단적 관심사)가 타겟 메소드의 시점(조인포인트)를 감지하여 자동 실행되게 할 수 있다. 

사용자(코드 호출)
   ↓
스프링 AOP 프록시 (Advice 적용)
   ↓
Advice 실행 - Advice 메서드의 파라미터로 JoinPoint 주입
   ↓
JoinPoint를 통해 타겟 메서드 정보/파라미터/객체 접근 가능
   ↓
핵심 로직(Target 메서드) 실행

4. 실행 결과

1. Before적용

2. After적용

3. Around적용

profile
DB가 좋아요

0개의 댓글