인프런에서 백기선님의 스프링 프레임워크 핵심 기술 을 공부하며 개인적으로 정리한 글입니다.
AOP 란
개념
AOP(Aspect-Oriented Programming) 는 OOP를 보완하는 수단으로, 흩어진 Aspect 를 모듈화 할 수 있는 프로그래밍 기법이다.
즉, 여러 곳에서 쓰이는 공통 기능을 모듈화하고, 쓰이는 곳에 필요할 때 연결함으로써, 유지 보수 혹은 재사용에 용이하도록 프로그래밍 하는 것.
주요 개념
- Aspect
- 여러 곳에서 쓰이는 코드(공통 부분)를 모듈화한 것
- Target
- Aspect 가 적용되는 곳
- Advice
- Aspect 에서 실질적인 기능에 대한 구현체
- Joint point
- Advice 가 Target 에 적용되는 시점
- 메서드 진입할 때, 생성자 호출할 때, 필드에서 값을 꺼낼 때 등등
- Point cut
- Joint point 의 상세 스펙을 정의한 것
AOP 구현체
- AspectJ
- 스프링 AOP
AOP 적용 방법
- 컴파일 (AspectJ)
- 로드 타임 (AspectJ)
- 런타임 (스프링 AOP)
스프링 AOP
특징
- 프록시 기반의 AOP 구현체
- 스프링 빈에만 AOP 를 적용할 수 있다.
- 동적 프록시 빈을 만들어 등록시켜준다.
- 빈 라이프사이클 중 실행되는
BeanPostProcessor
구현체를 구현함 AbstractAutoProxyCreator implements BeanPostProcessor
- 빈 라이프사이클 중 실행되는
구현
먼저 다음 AOP 를 위해 다음 의존성을 추가한다.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
간단한 인터페이스를 구현하는 클래스 하나를 빈으로 정의해보자.
public interface EventService {
public void created();
public void operation();
public void deleted();
}
@Component
public class SimpleServiceEvent implements EventService {
@Override
public void created() {
long begin = System.currentTimeMillis();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("created");
System.out.println(System.currentTimeMillis() - begin);
}
@Override
public void operation() {
System.out.println(System.currentTimeMillis() - begin);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("operation");
System.out.println(System.currentTimeMillis() - begin);
}
@Override
public void deleted() {
System.out.println("deleted");
}
}
created()
와 operation()
안에는 수행시간을 측정하는 코드를 담고있다.
이제 이 클래스를 활용하는 Runner
를 다음과 같이 만들자.
@Component
public class AppRuner implements ApplicationRunner {
@Autowired
EventService eventService;
@Override
public void run(ApplicationArguments args) throws Exception {
eventService.created();
eventService.operation();
eventService.deleted();
}
}
실행하면 다음과 같은 결과가 출력된다.
created
1011
operation
2004
deleted
1) execution expression
이제 이를 AOP 를 이용하여 개선해보자.
위 코드를 보면 수행 시간을 재는 다음 코드가 여러 곳에서 사용되고 있다.
long begin = System.currentTimeMillis();
... (수행) ...
System.out.println(System.currentTimeMillis() - begin);
이 코드는 Aspect 로 묶어서 관리될 필요가 있어보인다.
이제 다음과 같이 Aspect 클래스를 정의하자.
// Aspect 정의
@Component
@Aspect
public class PerfAspect {
// Advice 정의 (Around 사용)
// Point Cut 표현식
// (com.example.demo 밑에 있는 모든 클래스 중 EventService 안에 들어있는 모든 메쏘드에 이 행위를 적용하라.)
@Around("execution(* com.example..*.EventService.*(..))")
public Object logPerf(ProceedingJoinPoint pjp) throws Throwable {
long begin = System.currentTimeMillis();
Object retVal = pjp.proceed();
System.out.println(System.currentTimeMillis() - begin);
return retVal;
}
}
@Aspect
로 Aspect 클래스임을 정의한다.- Advice 를 정의한다. Point cut 시점은
@Around
형태로 정의했다.
이 외에도 다음과 같은 어노테이션이 있다.@Around
@Before
@After
execution...
으로 시작하는 부분은 execution expression 이라고 한다.
Point cut을 설정하는 부분이다.
AOP 를 적용했으므로, 이제 적용될 클래스에서 Aspect 로 대체된 부분은 삭제한다.
@Component
public class SimpleServiceEvent implements EventService {
@Override
public void created() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("created");
}
@Override
public void operation() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("operation");
}
@Override
public void deleted() {
System.out.println("deleted");
}
}
이제 다시 Runner
를 실행하면 다음과 같이 출력된다.
created
1011
operation
2004
deleted
0
2) annotaion 기반
위와 같이 했더니 하나 문제가 있다.
우리는 created()
와 operation()
에만 Apsect 를 적용하고 싶은데 delete()
에도 적용이 됐기 때문이다.
이를 어노테이션 기반 Advice 정의로 해결해보자.
먼저 다음과 같은 어노테이션을 만든다.
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.CLASS)
public @interface PerfLogging {
}
Aspect 클래스를 다음과 같이 수정한다.
@Component
@Aspect
public class PerfAspect {
@Around("@annotation(PerfLogging)")
public Object logPerf(ProceedingJoinPoint pjp) throws Throwable {
long begin = System.currentTimeMillis();
Object retVal = pjp.proceed();
System.out.println(System.currentTimeMillis() - begin);
return retVal;
}
}
execution expression 을 "@annotation(PerfLogging)")
으로 대체한 것이다.
이제 적용될 클래스의 메쏘드에 @PerfLogging
을 붙여주자.
@Component
public class SimpleServiceEvent implements EventService {
@PerfLogging
@Override
public void created() {
...
}
@PerfLogging
@Override
public void operation() {
...
}
@Override
public void deleted() {
...
}
}
다시 Runner
를 실행하면 @PerfLogging
을 붙인 메쏘드만 Aspect 가 붙는 것을 확인할 수 있다.
created
1011
operation
2004
deleted
3) 특정 bean 기반
다음과 같이 simpleServiceEvent
내 모든 public
메쏘드에다가 적용하는 방법도 있다.
// Aspect 정의
@Component
@Aspect
public class PerfAspect {
// 빈이 가지고있는 모든 퍼블릭 메쏘드
@Around("bean(simpleServiceEvent)")
public Object logPerf(ProceedingJoinPoint pjp) throws Throwable {
long begin = System.currentTimeMillis();
Object retVal = pjp.proceed();
System.out.println(System.currentTimeMillis() - begin);
return retVal;
}
}
created
1011
operation
2004
deleted
0
'더 나은 엔지니어가 되기 위해 > 지금은 안쓰는 자바' 카테고리의 다른 글
[스프링 프레임워크 핵심 기술] SpEL (0) | 2020.02.22 |
---|---|
[더 자바] 바이트 코드 조작 (0) | 2020.02.22 |
[더 자바] JVM 이해하기 (0) | 2020.02.21 |
[스프링 프레임워크 핵심 기술] 데이터 바인딩 추상화 (0) | 2020.02.21 |
[스프링 프레임워크 핵심 기술] Resource / Validation 추상화 (0) | 2020.02.21 |