Spring에는 Spring Triangle이라고 하는 AOP, IoC/DI, PSA가 있다. 이번 시간에는 AOP에 대해 알아보자.
AOP(Aspect Oriented Programming)
AOP(Aspect Oriented Programming)는 '관점 지향 프로그래밍'이라고 한다.
여기서 말하는 관점 지향이란?
→ 어떤 로직을 기준으로 핵심적인 관점과 부가적인 관점으로 나눠서 그 관점을 기준으로 모듈화 하겠다는 것이다.
그러면 모듈화란?
→ 공통된 로직이나 기능을 하나로 묶는 것이다.
핵심적인 관점과 부가적인 관점은 어떤 것을 의미하는가?
→ 핵심적인 관점은 우리가 적용하고자 하는 핵심적인 비즈니스 로직을 뜻한다. 반면에 부가적인 관점은 핵심적인 관점이 행해지기 위한 것으로 볼 수 있다.
위 그림을 보면 Class A , Class B , Class C에 중복 사용되는 코드를 색깔을 다르게 하여 구분해놓은 것을 볼 수 있다.
이렇게 반복 사용되는 코드를 흩어진 관심사(CrossCutting Concerns)라고 한다.
이처럼 흩어진 관심사를 Aspect로 모듈화하고 핵심적인 비즈니스 로직에서 분리하여 재사용하겠다는 것이 AOP의 목적이다.
AOP의 주요 개념
○ Aspect
→ 흩어진 관심사를 묶어서 모듈화 한 것이다. Advice와 Point Cut이 들어간다.
○ Target
→ Aspect가 가지고 있는 Advice의 적용 대상(Class , Method etc....)을 가리킨다.
○ Advice
→ 어떤 일을 해야 할지에 대한 정보를 가지고 있다.
○ Join Point
→ Advice가 적용될 위치를 말한다.
○ Point Cut
→ Join Point의 상세한 스펙을 정의한 것이다. 구체적으로 Advice가 실행될 지점을 정할 수 있다.
AOP 적용 시점
1. 컴파일 타임 적용
→ 컴파일 시점에 AOP가 적용된 바이트 코드를 생성하는 방법.
2. 로드 타임 적용
→ 컴파일 후 클래스를 로딩하는 과정에서 클래스 정보를 변경하는 방법.
3. 런타임 적용
→ 스프링 AOP가 자주 사용하는 방법. Bean을 만들 때 해당 타입의 Proxy Bean을 만들어 Proxy Bean이 Aspect 코드를 추가하여 동작하는 방법.
스프링 AOP의 특징
- 프록시 기반의 AOP 구현체 → 접근 제어와 부가 기능을 추가하기 위함.
- 스프링 AOP는 Bean에만 적용.
- 모든 AOP기능을 제공하기보단 중복 코드, 프록시 클래스 작성의 번거로움 등 흔한 문제를 해결하기 위함.
프록시 패턴
프록시 패턴에는 interface가 존재하고 Cilent는 이 interface 타입으로 프록시 객체를 사용한다.
프록시 객체와 기존 타겟 객체(Real Subject)는 같은 타입이고 Proxy 객체는 원래 해야 할 일을 가지고 있는 타겟 객체를 감싸서 Cilent의 요청을 처리한다.
스프링 AOP 구현
Spring AOP를 구현하기 위하여 아래 의존성을 추가해주었다.
implementation 'org.springframework.boot:spring-boot-starter-aop'
AspectEx.java
@Component // 스프링 AOP를 위해 Bean 등록
@Aspect // 해당 클래스가 Aspect라는 것을 명시
public class AspectEx {
@Around("execution(* com.example..*.EventService.*(..))")
// com.example 아래 모든 클래스와 EventService 밑의 모든 메서드에 적용.
public Object logPerf(ProceedingJoinPoint pjp) throws Throwable{
long begin = System.currentTimeMillis();
Object proceed = pjp.proceed();
//ProceedingJoinPoint는 Advice가 적용되는 대상. 즉, EventService의 메서드들
System.out.println(System.currentTimeMillis() - begin);
return proceed;
}
}
EventService.java
public interface EventService {
void createEvent();
void publishEvent();
void deleteEvent();
}
EventServiceImpl.java
@Service
public class EventServiceImpl implements EventService {
@Override
public void createEvent() {
try{
Thread.sleep(1000);
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println("Created Event!");
}
@Override
public void publishEvent() {
try{
Thread.sleep(2000);
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println("Published Event!");
}
@Override
public void deleteEvent() {
System.out.println("Deleted Event!");
}
}
AppRunner.java
@Component
public class AppRunner implements ApplicationRunner {
private EventService eventService;
public AppRunner(EventService eventService){
this.eventService = eventService;
}
@Override
public void run(ApplicationArguments args) throws Exception {
eventService.createEvent();
eventService.publishEvent();
eventService.deleteEvent();
}
}
실행 결과
이번에는 위와 같이 경로를 지정하는 방식이 아닌 어노테이션이 붙은 메서드에만 적용하는 방법을 보자.
AspectEx.java
@Component
@Aspect
public class AspectEx {
@Around("@annotation(PerfLogging)") // @PerfLogging 어노테이션이 붙은 메서드만 적용
public Object logPerf(ProceedingJoinPoint pjp) throws Throwable{
long begin = System.currentTimeMillis();
Object proceed = pjp.proceed();
System.out.println(System.currentTimeMillis() - begin);
return proceed;
}
}
PerfLogging.java
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.CLASS)
public @interface PerfLogging {
}
EventServiceImpl.java
@Service
public class EventServiceImpl implements EventService {
@PerfLogging
@Override
public void createEvent() {
try{
Thread.sleep(1000);
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println("Created Event!");
}
@Override
public void publishEvent() {
try{
Thread.sleep(2000);
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println("Published Event!");
}
@Override
public void deleteEvent() {
System.out.println("Deleted Event!");
}
}
실행 결과
createEvent()에만 @PerfLogging을 적용해주었더니 이전 결과와 달리 PubilshEvent()에는 Aspect가 적용되지 않은 결과를 보여준다.
또한 특정 Bean 전체에 Aspect를 적용시킬 수 있다.
@Component
@Aspect
public class AspectEx {
@Around("bean(EventServiceImpl)")
public Object logPerf(ProceedingJoinPoint pjp) throws Throwable{
long begin = System.currentTimeMillis();
Object proceed = pjp.proceed();
System.out.println(System.currentTimeMillis() - begin);
return proceed;
}
}
위처럼 @Around에 적용할 Bean을 명시해준다. 그럼 해당 Bean이 가지고 있는 모든 Public 메서드에 Aspect가 적용된다.
@Around 어노테이션 외에 Aspect 적용 시점을 지정할 수 있는 어노테이션이 다음과 같이 있다.
- @Before(이전) : 타겟 메서드가 호출되기 전에 Advice 기능 수행
- @After(이후) : 타겟 메서드의 결과와 상관없이 타겟 메서드 완료 후 Advice 기능 수행
- @AfterRunning(정상적 반환 이후) : 타겟 메서드가 성공적인 결과를 반환한 후 Advice 기능 수행
- @AfterThrowing(예외 발생 이후) : 타겟 메서드가 수행 중 예외 처리를 던지면 Advice 기능 수행
- @Around(메서드 실행 전후) : Advice가 타겟 메서드를 감싸서 타겟 메서드 호출 전/후에 Advice 기능 수행
▽ 도움을 주신 분들
'Spring > Spring' 카테고리의 다른 글
[Spring] Spring Triangle? 그게 뭔데 - PSA (0) | 2022.07.05 |
---|---|
[Spring] Spring Triangle? 그게 뭔데 - IoC/DI (0) | 2022.06.30 |
[Spring] 인터셉터(Interceptor)에 대해 알아보자! (0) | 2022.06.28 |
[Spring] Filter에 대해 알아보자 ! (0) | 2022.06.24 |
[spring] Spring MVC 구조 (0) | 2022.06.02 |