AOP란?
- 관점 지향 프로그래밍
- 공통된 부분을 모듈화 하여 처리하는 개념
- 로깅, 인코딩, 보안, 에러체크등 공통부분을 분리해서 재사용 하자!
프록시 패턴
- 스프링 AOP를 사용하는 방법
- 공통 모듈을 프록시로 만들어서 DI로 연결된 빈 사이에 적용
- 프록시 객체를 만들어서 대신 처리하는 방식
용어정리
Compile Time
Advice
Pointcut
- 특정 포인트, AOP를 어느 시점에 적용할 것인가
Aspect
- Advice + Pointcut
- 공통 기능을 의미한다
Weaber
Runtime
Join Point
- 실행되는 인스턴스
- Pointcut에 정의된 조건이 참이면 호출
- 메소드 호출과 관련된 정보를 가진다.
사용하기
dependencies
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-aop'
}
@Aspect
@Configuration
@Aspect
public class LoggingAspect {
}
- 다음과 같이 명시해서 AOP를 수행하는 클래스임을 선언한다.
@Pointcut
@Point("execution(* com.chan.ssb.*.*.*(..))")
- 다음과 같이 실행할 시점을 설정하는 함수로 많은 설정방법이 있다.
@Before
@Before("execution(* com.chan.ssb.*.*.*(..))")
public void logMethodCallBefore(JoinPoint joinPoint) {
logger.info("Before Aspect - {}", joinPoint);
}
@After
- 메소드 실행 이후 수행할 작업을 지정한다.
- 메소드의 실패, 성공과 상관없이 실행한다.
@After("execution(* com.chan.ssb.*.*.*(..))")
public void logMethodCallAfter(JoinPoint joinPoint) {
logger.info("After Aspect - {} is called", joinPoint);
}
@AfterReturning
@AfterReturning(
pointcut = "execution(* com.chan.ssb.*.*.*(..))",
returning = "resultValue"
)
public void logMethodCallAfterReturning(JoinPoint joinPoint, Object resultValue) {
logger.info("AfterReturning Aspect - {} is called {}", joinPoint, resultValue);
}
@AfterThrowing
@AfterThrowing(
pointcut = "execution(* com.chan.ssb.*.*.*(..))",
throwing = "exception"
)
public void logMethodCallAfterThrowing(JoinPoint joinPoint, Exception exception) {
logger.info("AfterThrowing Aspect - {} is called {}", joinPoint, exception);
}
@Around
@Around("execution(* com.chan.ssb.*.*.*(..))")
public Object logMethodCallAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
logger.info("Around Aspect before - {} is called", proceedingJoinPoint);
Object returnValue = proceedingJoinPoint.proceed();
logger.info("Around Aspect after - {} is called", proceedingJoinPoint);
return returnValue;
}
- ProceedingJoinPoint을 이용해서 메소드를 실행 시킬 수 있다.
Pointcut 관리하기
- 지금과 같이 Pointcut을 사용하는 것은 코드의 변경시 오류를 발생하고 좋지 않다.
- CommonPointcutConfig을 생성해서 관리해보자
- CommonPointcutConfig.java
package com.chan.ssb.aspect;
import org.aspectj.lang.annotation.Pointcut;
public class CommonPointcutConfig {
@Pointcut("execution(* com.chan.ssb.player.*.*(..))")
public void playerPackageConfig() {}
@Pointcut("execution(* com.chan.ssb.user.*.*(..))")
public void userPackageConfig() {}
@Pointcut("execution(* com.chan.ssb.team.*.*(..))")
public void teamPackageConfig() {}
@Pointcut("bean(*Service*)")
public void allPackageConfigUsingBean() {}
@Pointcut("@annotation(com.chan.ssb.aspect.MyLog)")
public void myLogAnnotation() {}
}
@Before("com.chan.ssb.aspect.CommonPointcutConfig.playerPackageConfig()")
- 다음과 같이 정의해서 사용할 수 있다.
- ("bean(Service)"): Service를 포함하는 모든 빈을 선택
- ("@annotation(com.chan.ssb.aspect.MyLog)"): 사용자가 정의한 어노테이션을 선택한다.
어노테이션 정의 하기
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyLog {
}
- @Target: 적용될 대상을 지정
- @Retention: 대상의 메모리를 언제까지 유지할 것인지 결정하는 애노테이션
- 사용하기
@MyLog
@GetMapping("")