[Spring] 스프링 AOP 개념 이해 및 적용 방법
1. AOP(Aspect Oriented Programming)
Spring은 Spring Triangle이라고 부르는 세 가지 개념을 제공해준다. 각각 IoC, AOP, PSA를 일컫는다.
AOP는 Aspect Oriented Programming의 약자로 '측면/양상 지향적인 프로그래밍'이라는 의미이다.
'측면/양상 지향 프로그래밍'이 무엇을 의미하는가?
class A {
method a() {
AAAA
method a가 하는 일들
BBBB
}
method b() {
AAAA
method b가 하는 일들
BBBB
}
}
class B {
method c() {
AAAA
method c가 하는 일들
BBBB
}
}
|
cs |
위와 같이 동일한 일을 하는 코드 AAAA, BBBB가 여기저기서 사용되고 이렇게 흩어져있으면 코드 변경이 필요한 경우 일일이 다 찾아서 바꿔야한다.
AOP는 그렇게 하지 않고 여러군데서 사용되는 중복되는 코드를 떼어내서 분리하고, method a, b, c는 자신이 해야할 작업만 갖고있자는 개념이다.
여기서 '여러군데서 사용되는 중복되는 코드'가 AOP에서 말하는 'aspect'라고 이해하면 된다.
2. 프록시 패턴
Spring AOP는 프록시 패턴이라는 디자인 패턴을 사용해서 AOP 효과를 낸다.
프록시 패턴을 사용하면 어떤 기능을 추가하려 할때 기존 코드를 변경하지 않고 기능을 추가할수 있다.
어떤 클래스가 Spring AOP의 대상이라면 그 기존 클래스의 빈이 만들어질때 Spring AOP가 프록시(기능이 추가된 클래스)를 자동으로 만들고 원본 클래스 대신 프록시를 빈으로 등록한다.
그리고 원본 클래스가 사용되는 지점에서 프록시를 대신 사용한다.
Spring의 PetClinic 예제에서 OwnerRepository 인터페이스의 @Transactional 애노테이션이 이에 해당한다.
@Transactional 애노테이션이 붙어있으면 OwnerRepository 타입의 프록시가 새로 만들어지고 Spring AOP에 의해 자동으로 생성되는 OwnerRepository의 프록시에는 @Transactional 애노테이션이 지시하는 코드가 삽입된다.
@Transactional 애노테이션에 의해 추가되는 기능은 다음과 같다.
JDBC에서 트랜잭션 처리를 하려면 SQL 실행문 앞뒤에 setAutoCommit()와 commit()/rollback() 코드가 항상 붙는데 @Transactional 애노테이션은 프록시에 자동으로 그 코드를 넣어서 반복, 중복되는 코드를 생략할 수 있게하는 것이다.
이로 인해 개발자는 비즈니스 로직에만 집중할 수 있게 된다.
3. Spring AOP 적용 예제
Spring이 제공하는 애노테이션 기반의 AOP를 직접 적용해보자.
Spring PetClinic 예제의 OwnerController의 메소드들에 메소드 성능을 측정하는 기능을 추가할 것이다.
- [Spring] 스프링 예제 프로젝트 PetClinic 빌드 및 실행하기
우선 성능을 측정하고자 하는 메소드에 @LogExecutionTime 애노테이션을 붙인다.
@LogExecutionTime은 제공되는 애노테이션이 아닌 새로 정의해야하는 애노테이션이므로 지금은 컴파일 에러가 발생한다.
Alt + Enter를 눌러 quick fixes를 띄우고 Create annotation 'LogExecutionTime'을 클릭한다.
OK를 클릭한다.
빈 애노테이션이 만들어졌다.
아래와 같이 애노테이션을 정의하고 몇가지 설정을 추가한다.
1
2
3
4
5
6
7
8
9
10
11
|
package org.springframework.samples.petclinic.owner;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LogExecutionTime {
}
|
cs |
@Target(ElementType.METHOD)
애노테이션을 메소드에 사용할 것이라고 설정한다.
@Retention(RetentionPolicy.RUNTIME)
애노테이션이 RUNTIME까지 유지되도록 설정한다.
어떤 메소드에 AOP를 적용할건지 알려주는 애노테이션을 정의하였다.
이제 해당 메소드에 어떤 기능을 추가할 것인지를 알려주는 실제 aspect를 구현해야한다.
같은 패키지에 LogAspect 클래스를 만들고 아래와 같이 작성한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
package org.springframework.samples.petclinic.owner;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.util.StopWatch;
@Component
@Aspect
public class LogAspect {
Logger logger = LoggerFactory.getLogger(LogAspect.class);
@Around("@annotation(LogExecutionTime)")
public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
// @LogExecutionTime 애노테이션이 붙어있는 타겟 메소드를 실행
Object proceed = joinPoint.proceed();
stopWatch.stop();
logger.info(stopWatch.prettyPrint());
return proceed; // 결과 리턴
}
}
|
cs |
Logger logger = LoggerFactory.getLogger(LogAspect.class);
slf4j로 로거를 만든다.
@Around("@annotation(LogExecutionTime)")
이 애노테이션을 붙인 메소드에서는 ProceedingJoinPoint 파라미터를 받을 수 있다.
애노테이션의 value를 "@annotation(LogExecutionTime)"로 지정함으로서
joinPoint는 @LogExecutionTime를 붙인 타겟 메소드를 의미하게 된다.
Object proceed = joinPoint.proceed();
타겟 메소드를 실행한다.
이 라인 앞 뒤로 StopWatch를 이용한 메소드 성능 측정 코드를 넣어준다.
어플리케이션을 실행하여 결과를 확인해보자.
StopWatch를 사용한 메소드 성능 측정 결과
위와 같이 OwnerController의 메소드 실행 시 LogAspect에 추가한 메소드 성능 측정 결과가 콘솔에 출력된다.
이것이 aspect이며 Spring이 제공해주는 애노테이션 기반의 AOP이다.
이렇게 적용한 AOP는 내부적으로 프록시 패턴 기반으로 동작한다.
OwnerController(타겟)에는 애노테이션 외에는 아무런 코드가 추가되지 않았다.
'Programming > Spring' 카테고리의 다른 글
[Spring] 스프링 빈(Bean)의 개념과 생성 원리 (0) | 2022.05.29 |
---|---|
[Spring] 의존성 주입(DI, Dependency Injection)의 세가지 방법 (0) | 2022.05.29 |
[Spring] 스프링을 시작하며 : 스프링 프레임워크의 개념과 특징, MVC 구조, 스프링 부트 (Spring Framework, Spring MVC, Spring boot) (0) | 2022.05.29 |
[스프링부트 (1)] 스프링부트 시작하기 (SpringBoot 프로젝트 설정 방법) (0) | 2022.05.29 |
[스프링부트 (12)] SpringBoot 에러 페이지 설정(Custom Error Page) (0) | 2022.05.29 |