AOP는 무엇인가?
AOP (Aspect-Oriented Programming)
--
AOP는
관점 지향 프로그래밍으로
비즈니스 로직과 상관없는 공통 기능을 분리하여
재사용성과 유지보수성을 높이는 프로그래밍 기법이다.
이는 OOP(객체 지향 프로그래밍)에서 해결하기 어려운 공통 기능의 중복 문제를 해결하는 데 유용하다.
공통 기능이란
"횡단 관심사 (Cross-cutting Concerns)"를 말하고
여러 모듈이나 클래스에서 공통으로 필요하지만 해당 모듈 자체의 핵심 기능과는
직접적으로 관련이 없는 기능을 의미한다.
(ex. 로깅)
예시 상황
- m1(), m2(), ... 라는 자판기 관련 메서드가 존재 (m1 = 커피 자판기 메서드, m2 = 음료 자판기 메서드, m3 = ...)
- m1(), m2(), ... 메서드에 공통적으로 자판기에서 제품이 나오기까지 걸리는 시간을 로그로 출력 코드 존재
위와 같은 상황에서
각 메서드에 모두 핵심 기능과는 관련 없지만 공통적으로 시간을 측정하는 로그 코드가 존재한다.
이러한 경우 각 자판기 관련 메서드에는 시간 측정 로그 코드가 반복적으로 작성해야 하는 번거로움과
핵심 기능과 관련 없는 코드로 인해 코드 가독성 및 유지보수성도 떨어지게 된다.
이를 해결하기 위해 AOP를 사용하면 아래와 같은 문제가 해결된다.
- 중복 코드 제거 : 로깅, 트랜잭션 관리, 보안 검사 등의 기능을 여러 모듈/클래스에서 반복적으로 구현하는 것을 방지
- 핵심 로직과 보조 기능(공통 기능) 분리 : 코드 가독성과 유지보수성 향상
- 결합도 낮춤 : 비즈니스 로직과 공통 기능을 분리하여 결합도를 줄이고 모듈화 가능
즉, 핵심 기능과 공통 기능을 분리하고 공통 기능을 따로 모듈화 하여 변경 지점이 하나로 만든다.
(공통 기능 모듈에서 코드 수정하면 모든 자판기 관련 메서드에 변경 사항 적용)
--
AOP의 주요 개념
--
Aspect (애스펙트)
Advice와 Pointcut을 결합한 것을 의미한다.
즉, 횡당 관심사(공통 기능)와 해당 로직을 어디서 실행할지(대상)를 결합한 객체로
간단히 공통 기능을 모아둔 모듈을 의미한다.
ex) 로깅, 트랜잭션 관리, 권한 체크 등의 기능(모듈)
Advice (어드바이스)
실제로 실행될 코드(공통 기능, Aspect의 동작)를 의미한다.
즉, 횡단 관심자(로직)를 구현한 구현체(모듈/클래스)다.
추가로 횡단 관심자(로직)가 언제 실행될지 정의하는 역할도 한다.
대표적인 실행 시점 종류
- @Before : 메서드 실행 이전에 실행
- @After : 메서드 실행 후에 실행 (성공/실패 관계없이)
- @After-Returning : 메서드가 정상적으로 실행된 후 실행
- @After-Throwing : 예외가 발생했을 때 실행
- @Around : 메서드 실행 전후 또는 실행 자체를 제어
Pointcut (포인트 컷)
Join Point 중에서 실제로 Advice를 적용할 지점을 정의하는 표현식으로
횡단 관심사(로직)가 수행될 위치(대상)를 의미한다.
즉, 실행 가능한 모든 Join Point 중에서 특정 대상만 선택하는 필터 역할을 한다.
Join Point (조인 포인트)
AOP가 적용될 수 있는 지점을 의미한다.
즉, Advice가 실행될 수 있는 "모든 실행 가능한 지점"을 가리킨다.
Weaving (위빙)
Advice를 주기능에 적용하는 행위로
Advice와 Pointcut을 합쳐서(Aspect를) 실제로 객체에 적용하는 과정을 의미한다.
위빙 방식
- 컴파일 타입 위빙 : AspectJ 사용 시, 컴파일 단계에서 코드 삽입
- 런타임 위빙 : Spring AOP에서 사용 (프록시를 이용하여 런타임에 적용)
AspectJ는
Aspect 개념을 "자바"에서 사용하여 AOP를 적용하도록 만든 프레임워크
Spring AOP는
Aspect 개념을 "스프링"에 사용하여 AOP를 적용하도록 만든 프레임워크
(Spring AOP에서는 Aspect를 Advisor라고 부른다.)
--
Spring AOP 사용 방법
--
1. Spring AOP 의존성 추가
// build.gradle
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-aop'
}
2. AOP가 적용될 서비스 클래스
import org.springframework.stereotype.Service;
@Service // Spring Bean 등록
public class SampleService {
public void m1() { // [Join Point] AOP가 적용될 대상 메서드
System.out.println("비즈니스 로직 실행");
}
public void m2() { // [Join Point] AOP가 적용될 대상 메서드
System.out.println("비즈니스 로직 실행");
}
}
m1(), m2 메서드는
ExecutionTimeAspect 클래스의 Pointcut에서 지정한 조건과 일치하기 때문에
AOP가 적용되는 지점(메서드)으로 Join Point가 된다.
3. AOP 구현 (횡단 관심사 분리)
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
@Aspect // [Aspect] AOP 기능을 하는 클래스 정의
@Component // Spring Bean 등록
public class ExecutionTimeAspect {
@Around("execution(* com.example.service..m*(..))") // [Pointcut] 특정 패키지의 m으로 시작하는 모든 메서드에 적용
public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable { // [Advice] 실행 시간을 측정하는 메서드
long startTime = System.currentTimeMillis(); // 메서드 실행 시작 시간 기록
Object result = joinPoint.proceed(); // [JoinPoint] 원래의 비즈니스 로직(메서드) 실행
long endTime = System.currentTimeMillis(); // 메서드 실행 종료 시간 기록
System.out.println(joinPoint.getSignature() + " 실행 시간: " + (endTime - startTime) + "ms");
return result; // 원래의 메서드 결과 반환
}
}
@Aspect
해당 클래스를 AOP 기능을 하는 클래스(Aspect)로 정의
@Component
해당 클래스를 Spring 빈으로 관리하도록 설정
@Around("execution(* com.example.service..m*(..))")
해당 로직(공통 기능)을 적용할 대상 메서드를 지정
- @Around : 원래의 메서드(특정 메서드)가 실행되기 전과 후에 공통 로직(기능)을 적용
- excution() : 메서드 실행 지점을 지정하는 표현식
- * : 반환 타입을 지정하는 위치로 메서드의 반환 타입에 상관없이 모든 메서드에 AOP를 적용
- com.example.service : 대상 메서드의 패키지 위치
- .. : 하위 패키지와 하위 클래스를 포함하는 와일드카드
- m* : m으로 시작하는 모든 메서드
- (..) : 매개변수를 의미하고 매개변수 타입을 지정할 수 있다. (..)은 모든 매개변수를 의미
즉, com.example.service 패키지와 그 하위에 있는 모든 클래스에서 m으로 시작하는 모든 메서드에 대해
AOP를 적용하겠다는 의미다.
logExecutionTime(ProceedingJoinPoint joinPoint)
AOP에서 실행될 공통 기능(로직)을 정의한 메서드
ProceedingJoinPoint joinPoint
AOP에서 실제 비즈니스 로직이 실행되는 시점을 나타낸다.
(joinPoint.proceed();를 호출하면 원래의 대상 메서드가 실행됨)
joinPoint.getSignature()
실행되는 메서드의 정보를 출력 (호출)
3. 실행 및 AOP 동작 확인
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
@SpringBootApplication // (Bean) Spring Boot 애플리케이션의 시작점
public class AopExampleApplication implements CommandLineRunner {
private final SampleService sampleService; // (Bean 주입) AOP가 적용된 SampleService를 주입받음
public AopExampleApplication(SampleService sampleService) { // (의존성 주입) 생성자 주입 방식
this.sampleService = sampleService;
}
public static void main(String[] args) {
SpringApplication.run(AopExampleApplication.class, args); // Spring Boot 애플리케이션 실행
}
@Override
public void run(String... args) { // (실행 메서드) 애플리케이션 실행 후 자동으로 실행됨
sampleService.execute(); // [JoinPoint] AOP가 적용된 execute() 메서드 호출
}
}
결과
비즈니스 로직 실행
public void com.example.service.SampleService.execute() 실행 시간: 3ms
--
'Terminology' 카테고리의 다른 글
STOMP (Simple/Streaming Text Oriented Messaging Protocol) (2) | 2024.12.01 |
---|---|
웹소켓 (WebSocket) (0) | 2024.11.25 |
SOP와 CORS (+ Origin) (0) | 2024.11.06 |
[디자인 패턴] 팩토리 메서드 패턴 (Factory Method) (1) | 2024.11.01 |
[디자인 패턴] 싱글톤 패턴 (Singleton) (0) | 2024.10.30 |
AOP는 무엇인가?
AOP (Aspect-Oriented Programming)
--
AOP는
관점 지향 프로그래밍으로
비즈니스 로직과 상관없는 공통 기능을 분리하여
재사용성과 유지보수성을 높이는 프로그래밍 기법이다.
이는 OOP(객체 지향 프로그래밍)에서 해결하기 어려운 공통 기능의 중복 문제를 해결하는 데 유용하다.
공통 기능이란
"횡단 관심사 (Cross-cutting Concerns)"를 말하고
여러 모듈이나 클래스에서 공통으로 필요하지만 해당 모듈 자체의 핵심 기능과는
직접적으로 관련이 없는 기능을 의미한다.
(ex. 로깅)
예시 상황
- m1(), m2(), ... 라는 자판기 관련 메서드가 존재 (m1 = 커피 자판기 메서드, m2 = 음료 자판기 메서드, m3 = ...)
- m1(), m2(), ... 메서드에 공통적으로 자판기에서 제품이 나오기까지 걸리는 시간을 로그로 출력 코드 존재
위와 같은 상황에서
각 메서드에 모두 핵심 기능과는 관련 없지만 공통적으로 시간을 측정하는 로그 코드가 존재한다.
이러한 경우 각 자판기 관련 메서드에는 시간 측정 로그 코드가 반복적으로 작성해야 하는 번거로움과
핵심 기능과 관련 없는 코드로 인해 코드 가독성 및 유지보수성도 떨어지게 된다.
이를 해결하기 위해 AOP를 사용하면 아래와 같은 문제가 해결된다.
- 중복 코드 제거 : 로깅, 트랜잭션 관리, 보안 검사 등의 기능을 여러 모듈/클래스에서 반복적으로 구현하는 것을 방지
- 핵심 로직과 보조 기능(공통 기능) 분리 : 코드 가독성과 유지보수성 향상
- 결합도 낮춤 : 비즈니스 로직과 공통 기능을 분리하여 결합도를 줄이고 모듈화 가능
즉, 핵심 기능과 공통 기능을 분리하고 공통 기능을 따로 모듈화 하여 변경 지점이 하나로 만든다.
(공통 기능 모듈에서 코드 수정하면 모든 자판기 관련 메서드에 변경 사항 적용)
--
AOP의 주요 개념
--
Aspect (애스펙트)
Advice와 Pointcut을 결합한 것을 의미한다.
즉, 횡당 관심사(공통 기능)와 해당 로직을 어디서 실행할지(대상)를 결합한 객체로
간단히 공통 기능을 모아둔 모듈을 의미한다.
ex) 로깅, 트랜잭션 관리, 권한 체크 등의 기능(모듈)
Advice (어드바이스)
실제로 실행될 코드(공통 기능, Aspect의 동작)를 의미한다.
즉, 횡단 관심자(로직)를 구현한 구현체(모듈/클래스)다.
추가로 횡단 관심자(로직)가 언제 실행될지 정의하는 역할도 한다.
대표적인 실행 시점 종류
- @Before : 메서드 실행 이전에 실행
- @After : 메서드 실행 후에 실행 (성공/실패 관계없이)
- @After-Returning : 메서드가 정상적으로 실행된 후 실행
- @After-Throwing : 예외가 발생했을 때 실행
- @Around : 메서드 실행 전후 또는 실행 자체를 제어
Pointcut (포인트 컷)
Join Point 중에서 실제로 Advice를 적용할 지점을 정의하는 표현식으로
횡단 관심사(로직)가 수행될 위치(대상)를 의미한다.
즉, 실행 가능한 모든 Join Point 중에서 특정 대상만 선택하는 필터 역할을 한다.
Join Point (조인 포인트)
AOP가 적용될 수 있는 지점을 의미한다.
즉, Advice가 실행될 수 있는 "모든 실행 가능한 지점"을 가리킨다.
Weaving (위빙)
Advice를 주기능에 적용하는 행위로
Advice와 Pointcut을 합쳐서(Aspect를) 실제로 객체에 적용하는 과정을 의미한다.
위빙 방식
- 컴파일 타입 위빙 : AspectJ 사용 시, 컴파일 단계에서 코드 삽입
- 런타임 위빙 : Spring AOP에서 사용 (프록시를 이용하여 런타임에 적용)
AspectJ는
Aspect 개념을 "자바"에서 사용하여 AOP를 적용하도록 만든 프레임워크
Spring AOP는
Aspect 개념을 "스프링"에 사용하여 AOP를 적용하도록 만든 프레임워크
(Spring AOP에서는 Aspect를 Advisor라고 부른다.)
--
Spring AOP 사용 방법
--
1. Spring AOP 의존성 추가
// build.gradle
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-aop'
}
2. AOP가 적용될 서비스 클래스
import org.springframework.stereotype.Service;
@Service // Spring Bean 등록
public class SampleService {
public void m1() { // [Join Point] AOP가 적용될 대상 메서드
System.out.println("비즈니스 로직 실행");
}
public void m2() { // [Join Point] AOP가 적용될 대상 메서드
System.out.println("비즈니스 로직 실행");
}
}
m1(), m2 메서드는
ExecutionTimeAspect 클래스의 Pointcut에서 지정한 조건과 일치하기 때문에
AOP가 적용되는 지점(메서드)으로 Join Point가 된다.
3. AOP 구현 (횡단 관심사 분리)
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
@Aspect // [Aspect] AOP 기능을 하는 클래스 정의
@Component // Spring Bean 등록
public class ExecutionTimeAspect {
@Around("execution(* com.example.service..m*(..))") // [Pointcut] 특정 패키지의 m으로 시작하는 모든 메서드에 적용
public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable { // [Advice] 실행 시간을 측정하는 메서드
long startTime = System.currentTimeMillis(); // 메서드 실행 시작 시간 기록
Object result = joinPoint.proceed(); // [JoinPoint] 원래의 비즈니스 로직(메서드) 실행
long endTime = System.currentTimeMillis(); // 메서드 실행 종료 시간 기록
System.out.println(joinPoint.getSignature() + " 실행 시간: " + (endTime - startTime) + "ms");
return result; // 원래의 메서드 결과 반환
}
}
@Aspect
해당 클래스를 AOP 기능을 하는 클래스(Aspect)로 정의
@Component
해당 클래스를 Spring 빈으로 관리하도록 설정
@Around("execution(* com.example.service..m*(..))")
해당 로직(공통 기능)을 적용할 대상 메서드를 지정
- @Around : 원래의 메서드(특정 메서드)가 실행되기 전과 후에 공통 로직(기능)을 적용
- excution() : 메서드 실행 지점을 지정하는 표현식
- * : 반환 타입을 지정하는 위치로 메서드의 반환 타입에 상관없이 모든 메서드에 AOP를 적용
- com.example.service : 대상 메서드의 패키지 위치
- .. : 하위 패키지와 하위 클래스를 포함하는 와일드카드
- m* : m으로 시작하는 모든 메서드
- (..) : 매개변수를 의미하고 매개변수 타입을 지정할 수 있다. (..)은 모든 매개변수를 의미
즉, com.example.service 패키지와 그 하위에 있는 모든 클래스에서 m으로 시작하는 모든 메서드에 대해
AOP를 적용하겠다는 의미다.
logExecutionTime(ProceedingJoinPoint joinPoint)
AOP에서 실행될 공통 기능(로직)을 정의한 메서드
ProceedingJoinPoint joinPoint
AOP에서 실제 비즈니스 로직이 실행되는 시점을 나타낸다.
(joinPoint.proceed();를 호출하면 원래의 대상 메서드가 실행됨)
joinPoint.getSignature()
실행되는 메서드의 정보를 출력 (호출)
3. 실행 및 AOP 동작 확인
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
@SpringBootApplication // (Bean) Spring Boot 애플리케이션의 시작점
public class AopExampleApplication implements CommandLineRunner {
private final SampleService sampleService; // (Bean 주입) AOP가 적용된 SampleService를 주입받음
public AopExampleApplication(SampleService sampleService) { // (의존성 주입) 생성자 주입 방식
this.sampleService = sampleService;
}
public static void main(String[] args) {
SpringApplication.run(AopExampleApplication.class, args); // Spring Boot 애플리케이션 실행
}
@Override
public void run(String... args) { // (실행 메서드) 애플리케이션 실행 후 자동으로 실행됨
sampleService.execute(); // [JoinPoint] AOP가 적용된 execute() 메서드 호출
}
}
결과
비즈니스 로직 실행
public void com.example.service.SampleService.execute() 실행 시간: 3ms
--
'Terminology' 카테고리의 다른 글
STOMP (Simple/Streaming Text Oriented Messaging Protocol) (2) | 2024.12.01 |
---|---|
웹소켓 (WebSocket) (0) | 2024.11.25 |
SOP와 CORS (+ Origin) (0) | 2024.11.06 |
[디자인 패턴] 팩토리 메서드 패턴 (Factory Method) (1) | 2024.11.01 |
[디자인 패턴] 싱글톤 패턴 (Singleton) (0) | 2024.10.30 |