프로그램을 중단점 관심사(핵심 기능 구현 코드)와 횡단적 관심사(예외처리, DB트랜잭션 제어, 로그 정보 출력 등 부수적 기능)을 구분하여, 횡단적 관심사를 여러 메소드 안에 넣지 않고, 코드를 재활용하기 위해 사용하는 프로그래밍 기법이다.
예를 들어 예외 처리는 모든 메소드에서 길면서, 동일한 내용의 코드를 반복해서 사용해야하는 단점이 있다. 따라서 횡단적 관심사를 분리하여 프로그래밍하는 Spring boot의 기능을 AOP라고 한다.
용어 | 내용 |
---|---|
Advice | 특정 타이밍에 실행되도록 설정한 횡단적 관심사를 구현한 코드 |
Join Point | Advice가 실행되는 장소 |
Point cut | 어떤 Join Point(위치)에서 advice(코드)가 실행될지 지정하는 조건 |
Aspect | Advice와 Pointcut을 결합한 클래스로, 횡단적 관심사를 어디서 어떻게 실행할지 정의 |
Interceptor | 함수호출을 포착하고, 그 전후에 어떤 처리를 하는 객체 |
Target | Aspect가 적용되는 대상으로, 중심적 관심사 이다. |
예를 들어
A클래스[B.X메서드 호출]
B클래스[X메서드(중단적관심사, 횡단적관심사)]
이러한 구성의 클래스 존재시, 아래처럼 횡단적 관심사를 분리하는 것이 가능하다.
A클래스[B.X메서드 호출]
B클래스[X메서드(중단적관심사)]
Aspect클래스[Advice(횡단적 관심사)]
이 때, A클래스에서 B클래스를 호출할 때, 내부적으로 AOP Proxy가 먼저 명령을 받고, B.X 의 중심적 관심사와 Aspect 클래스의 Advice를 호출하는 방식으로 Spring boot는 제어한다.
Advice는 횡단적 관심사의 구현 코드이다. 어떤 타이밍에 코드를 실행할지 지정할 수 있다.
애너테이션 | 내용 | 사용 예시 |
---|---|---|
@Before | 중심적 관심사 시작 전에 횡단적관심사 수행 | 로그인확인, 권한 확인 |
@AfterReturning | 중심적 관심사가 성공적으로 완료된 후 횡단적관심사 수행 | DB 트랜잭션 커밋, 성공 메시지 표시 |
@AlterThrowing | 중심적관심사에서 예외 발생 시 추가 처리내용 수행 | 오류 로그 기록, 오류메시지 표기 |
@After | 중심적관심사 실행 후(성공, 실패 상관없이) 수행 | 리소스헤제, 후처리 |
@Around | 중심적관심사 전 후로 수행 | 처리시간 측정, 트랜잭션 제어 |
포인트 컷은 어떤 위치에서 Advice가 실행될지 AOP proxy에게 알려주는 역할을 한다.
포인트 컷 설정 방법중 하나인 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를 선언 |
의존성 추가
1. Spring Boot DevTools
AOP 사용 세팅
AOP를 사용하기 위해서 build.gradle 파일, dependencies에 아래 코드를 추가한다.
implementation 'org.springframework.boot:spring-boot-starter-aop'
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이 자동으로 객체를 생성해준다.
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() : 이 함수를 통해 실제로 타깃(중심적 관심사) 메소드를 실행할 수 있다.
}
@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 메서드) 실행