Backend

Spring Core (4) AOP

wrallee 2020. 7. 3. 00:18

Spring AOP

Aspect-oriented Programming은 흩어진 Aspect를 모듈화 할 수 있는 프로그래밍 기법으로, OOP와는 상호 보완적인 관계이다. AOP의 구현체로 AspectJSpring AOP가 있는데, AspectJ가 좀 더 세분화 된 설정을 할 수 있는 구현체라고 보면 된다.

AOP 주요 개념

  • Aspect: 각각의 관점 모듈
    • Target: 적용 대상
  • Advice: 해야 할 일들(로직 처리)
  • Pointcut : 적용 할 구체적인 위치
    • JoinPoint: 합류 지점의 유형(Vue 라이프 사이클 훅과 비슷하다?)

AOP 적용 방식

  • 컴파일: 컴파일 시 Aspect를 Bytecode로 Class 파일에 포함시키는 방식
  • 로드 타임: Bytecode를 JVM에 로딩할 때 Aspect를 적용하는 방식
  • 런타임: Bean을 생성할 때 Proxy Bean을 만들어서 Aspect를 거쳐가도록 적용하는 방식

성능은 컴파일 > 로드 타임 > 런타임 순으로 좋다. 일반적으로 컴파일, 로드 타임은 AspectJ를 쓸 때 사용되는 방식이고, SpringAOP는 런타임을 사용한다.

1. Proxy 기반 AOP

Spring AOP는 프록시 기반의 AOP 구현체를 사용한다. 스프링 빈에만 AOP를 적용할 수 있으며, 스프링 IoC와 연동하여 아주 일반적인 문제를 해결하는 것이 주 목적이다(*프록시 패턴: 대상 객체를 프록시로 감싸서 기존 코드를 건드리지 않고 접근 제어 또는 부가기능을 추가하는 패턴).

프록시 클래스는 대상 객체와 같은 인터페이스를 상속받아야 한다.

@Primary
@Service
public class ProxySimpleEventService implements EventService {
    @Autowired
    SimpleEventService simpleEventService;

    @Override
    public void createEvent() {
        long begin = System.currentTimeMillis();
        simpleEventService.createEvent();
        long end = System.currentTimeMillies();
        System.out.println(end - begin);
    }
}

여기서 Target 객체는 SimpleEventService이다. 그러나 위 방식은 여러곳에 Aspect를 적용하고 싶을 때 프록시 클래스를 여러개로 만들어 하거나 중복 코드가 발생하는 등의 단점이 있다.

Spring에서는 이런 문제를 간편화 하기 위해 AbstractAutoProxyCreater라는 BeanPostProcessor의 구현체를 제공한다. AbstractAutoProxyCreater는 기존 빈을 대체하는 동적 프록시 빈을 만들어 등록해주는 역할을 수행한다.

2. @AOP

spring-boot-starter-aop 의존성 추가 후 관점 클래스 생성 후 @Aspect와 @Component 어노테이션을 붙여준다.

@Aspect
@Component
public class PerfAspect {
    @Around("execution(* com.wrallee..*.EventService.*(..))")
    public Object logPerf(ProceedingJoinPoint pjp) throws Throwable {
        long begin = System.currentTimeMillis();
        Object retVal = pjp.proceed();
        long end = System.currentTimeMillis();
        System.out.println(end - begin);
        return retVal;
    }
}

해당 클래스에서 특정 메소드만 빼거나 보다 개별적으로 적용하고 싶다면 어노테이션을 정의하는 방법이 있다. @Target 어노테이션을 사용하여 적용 대상(메소드, 클래스 등등)을 제한할 수 있다.

어노테이션을 정의 할 때 @Retention을 설정할 수 있는데, 기본값인 CLASS는 바이트코드에 어노테이션이 함께 들어가는 것을 의미한다(SOURCE는 빌드 후 사라지고, RUNTIME은 계속 유지된다).

@Documented
@Target(ElementType. METHOD)
public @interface PerLogging {
}
@Aspect
@Component
public class PerfAspect {
    @Around("@annotation(PerLogging)")
    public Object logPerf(ProceedingJoinPoint pjp) throws Throwable {
        long begin = System.currentTimeMillis();
        Object retVal = pjp.proceed();
        long end = System.currentTimeMillis();
        System.out.println(end - begin);
        return retVal;
    }
}
@Service
public class SimpleEventService implements EventService {
    @PerLogging
    @Override
    public void createEvent() {
        return;
    }

    @PerLogging
    @Override
    public void publishEvent() {
        return;
    }

    @Override
    public void deleteEvent() {
        return;
    }
}

어드바이스로 @Around 외에도 @Before, @AfterReturning, @AfterThrowing 등을 사용할 수 있다.

포인트컷 주요 표현식으로는 execution, @annotation, bean이 있으며 &&, ||, ! 등의 논리식도 사용할 수 있다(단, execution은 논리식이 안됨).

3. Null-safety

스프링 5.0에서는 Null 관련 어노테이션을 통해 컴파일 단계에서 NullPointerException을 방지할 수 있다. 일반적으로 아래 어노테이션들을 IDE에 별도로 설정해 주어야 제대로 인식한다.

  • @NonNull
  • @Nullable
  • @NonNullApi(패키지에 적용)
  • @NonNullFields(패키지에 적용)

[EventService.java]

public class EventService {
    @NonNull
    public String createEvent(@NonNull String name) {
        return "hello " + name;
    }
}

[package-info.java]

@NonNullApi
package com.wrallee.demospring51;

import org.springframework.lang.NonNullApi;