스프링 핵심 기술-13.스프링 AOP
스프링 AOP : 개념 소개
앞서 Spring Triangle에서 정리한 AOP 내용과 겹치는 내용들은 정리만 하고 넘어가고,
자세한 내용 및 설명은 이전 글을 참고하면 될 것 같다. (대부분 겹치는 내용)
Aspect-oriendted Programming (AOP)은 OOP를 보완하는 수단으로, 흩어진 Aspect를 모듈화 할 수 있는 프로그래밍 기법.
흩어진 관심사 (Crosscutting Concerns)
Class A, B, C
y x x
z y z
x y
AOP를 적용하면?
Aspect x y z
(a,b,c)(a,b)(a,c)
AOP 주요 개념
- Aspect(하나의 모듈)
- Target(적용 대상)
- Advice(해야할 일들)
- Pointcut(어디에 적용해야 하는지)
- C라는 클래스에 poo라는 메서드를 호출할 때만 이 aspect 를 작동하겠다. 알려주는 것.
- Join point(합류점)
- 실질적으로 가장 흔하게 사용하는 join point는 메서드 실행시점(끼어들 수 있는 지점)
생성자 호출 직전, 생성자 호출했을 때, 필드에 접근하기 전, 필드에 값을 가져갔을 때.. 등
스펙에 가깝다.(합류 지점)
- 실질적으로 가장 흔하게 사용하는 join point는 메서드 실행시점(끼어들 수 있는 지점)
AOP 구현체(Implementations)
- 굉장히 다양하다. 위키피디아
- 자바에서 구현된 것
- AspectJ
- 엄청나게 다양한 Join point, 다양한 기능들 제공
- 스프링 AOP
- 국한적인 기능만을 제공
- AspectJ
AOP 적용 방법
- 컴파일
- java -> (조작 - 조작이 된 바이트코드 생성) -> class
컴파일을 이미 했기 때문에 로드 타임이나 런타임에 성능적인 부하가 없다. 하지만 별도의 컴파일을 거쳐야 한다.
- java -> (조작 - 조작이 된 바이트코드 생성) -> class
- 로드 타임
- 순수한 클래스로 컴파일을 한 후(소스와 동일) 이후 로딩하는 시점에 로드타임위빙(로드타임에 뭔가를 끼워서 넣는 것)
로딩하는 jvm메서드 상에서는 뭔가를 끼워서 메모리상 올라가는 것.
클래스 로딩하는 시점에 약간의 부하와 로드타임위버(자바 에이전트)를 설정해줘야 한다.
하지만 AspectJ가 지원하는 다양한 문법을 사용할 수 있다.
- 순수한 클래스로 컴파일을 한 후(소스와 동일) 이후 로딩하는 시점에 로드타임위빙(로드타임에 뭔가를 끼워서 넣는 것)
- 런타임
- A라는 빈 대신 A를 감싸는 프록시 빈을 만든다.
이렇게 생성된 프록시 빈이 A의 메서드를 호출하기 전에, 하나의 모듈을 적용하고 A의 메서드를 호출.
최초 빈을 만드는 시점에 약간의 부하가 걸릴 수 있지만,
별도의 설정이나 문법이 쉽고, AOP의 공부를 할 필요가 없다.
(가장 합리적인 선택)
- A라는 빈 대신 A를 감싸는 프록시 빈을 만든다.
스프링 AOP : 프록시 기반 AOP
프록시 기반의 AOP 구현체이며, 스프링 빈에만 AOP를 적용할 수 있습니다.
모든 AOP 기능을 제공하는 것이 목적이 아니라,
스프링 IoC와 연동하여 엔터프라이즈 애플리케이션에서 가장 흔한 문제에 대한 해결책을 제공하는 것이 목적입니다.
앞서 말했듯 AspectJ에 비해 국한적인 기능만을 제공합니다.
프록시 패턴
이전 글의 프록시 패턴 예제 참조.
핵심은 기존 코드 변경 없이 접근 제어 또는 부가 기능 추가
프록시 패턴으로 AOP를 구현은 가능한데,
매번 프록시 클래스를 작성해야 하는가? 만약 여러 클래스 여러 메소드에 적용하려면?
그래서 등장한 것이 스프링 AOP 이다.
스프링 IoC 컨테이너가 제공하는 기반 시설과 Dynamic 프록시를 사용하여 여러 복잡한 문제 해결.
- 동적 프록시: 동적으로 프록시 객체 생성하는 방법
자바가 제공하는 방법은 인터페이스 기반 프록시 생성.
CGlib은 클래스 기반 프록시도 지원. - 스프링 IoC: 기존 빈을 대체하는 동적 프록시 빈을 만들어 등록 시켜준다.(클라이언트 코드 변경 없음)
AbstractAutoProxyCreator
AbstractAutoProxyCreator 는 BeanPostProcessor 구현체이다.
(AbstractAutoProxyCreator implements BeanPostProcessor)
BeanPostProcessor가 새로운 빈 인스턴스를 조작할 수 있는 기능을 제공한다.
즉, AbstractAutoProxyCreator가 빈 인스턴스를 감싼 다음에 AOP 프록시 빈을 생성 및 공통 로직 적용 후 본래 빈의 메서드를 실행한다.
스프링 AOP : @AOP
구현 예제
pom.xml (의존성 추가)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
@Component
@Aspect
public class PerfAspect {
// (Around) 메서드를 앞 뒤로 감싸 실행하는 경우
//@Around("execution(* com.example.ioccontainer5.demo.EvnetService.*())") // execution 표현식 com.example.ioccontainer5.demo.EventService 안에 있는 모든 메서드 적용
//@Around("@annotation(PerfLogging)") // @PerfLogging 붙인 모든 메서드
@Around("bean(simpleEventService)") // 해당 빈의 모든 메서드
public Object logPerf(ProceedingJoinPoint pjp) throws Throwable {
// ProceedingJoinPoint = 메서드 자체 (Throwable)메서드를 실해할 때 오류가 발생할 수 있다.
long begin = System.currentTimeMillis();
Object retVal = pjp.proceed(); // 메서드를 실행하는 메서드 (proceed)
System.out.println(System.currentTimeMillis() - begin);
return retVal;
}
// (Before) 메서드 실행 전
@Before("bean(simpleEventService)")
public void hello(){
System.out.println("before hello");
}
}
@Service
public class SimpleEventService implements EvnetService{
@Override
public void createEvent() {
try{
Thread.sleep(1000);
} catch (InterruptedException e){
e.printStackTrace();
}
System.out.println("create event");
}
@Override
public void publistEvent() {
try{
Thread.sleep(2000);
} catch (InterruptedException e){
e.printStackTrace();
}
System.out.println("create event");
}
@Override
public void deleteEvent() {
System.out.println("Delete event");
}
}
@Override
public void run(ApplicationArguments args) throws Exception {
evnetService.createEvent();
evnetService.publistEvent();
evnetService.deleteEvent();
}
실행 결과
before hello
create event
1010
before hello
create event
2000
before hello
Delete event
0
기타 정리
- Aspect 정의
- @Aspect
- 빈으로 등록해야 하니까 (컴포넌트 스캔을 사용한다면) @Component도 추가.
- 포인트컷 정의
- @Pointcut(표현식)
- 주요 표현식
- execution
- @annotation
- bean - 포인트컷 조합
&&, , !
- 어드바이스 정의
- @Before
- @AfterReturning
- @AfterThrowing
- @Around 그 메서드를 감싸는 형태로 실행됨 다용도