AOP란 관점 지향 프로그래밍으로 일반적으로 사용되는 클래스에서 중복되는 공통 부분(commit, rollback, log처리)을 별도의 영역으로 분리해서, 코드가 실행 되기 전이나 후의 시점에 해당 코드를 붙여넣음으로써 소스 코드의 중복을 줄이고, 필요할 때마다 가져다 쓸 수 있게 객체화하는 것을 의미한다.
여러 객체에 공통으로 적용되는 기능을 분리하여 작성한 클래스. Advice(실제 동작 코드) + Pointcut(적용된 메소드)이라고 볼 수 있다.
객체 생성 지점, 메소드 호출 지점, 예외 발생 지점 등 특정 작업의 시작점
JoinPoint에 삽입되어질 코드, 메소드
JoinPoint의 부분집합 / 실제 Advice가 적용되는 부분
작성한 Advice를 핵심 로직 코드에 삽입하는 것
대상 객체에 Advice가 적용된 후 생성되는 객체
Advice를 적용할 시 Spring은 대상 객체에 대한 프록시를 만들고 이 프록시는 대상 객체에 대한 호출을 가로채어 Advice의 부가 기능 로직을 수행하고 타겟의 핵심 로직을 호출(전처리)하거나, 타겟의 핵심 로직을 호출한 후에 Advice의 부가 기능을 수행(후처리)한다
전처리를 수행하는 어노테이션 pointcut은 JoinPoint를 통해 정보를 참조할 수 있다. JoinPoint타입 객체를 매개변수로 받아서 실행 시점 정보를 얻어올 수 있다.
후처리를 수행하는 어노테이션 pointcut은 JoinPoint를 통해 정보를 참조할 수 있다. JoinPoint타입 객체를 매개변수로 받아서 실행 시점 정보를 얻어올 수 있다.
// Pointcut을 작성해놓은 메서드
// -> Pointcut의 패턴이 작성되는 부분에 memberPointcut 메서드 이름을 작성하면
// @Pointcut에 작성된 패턴이 적용된다.
// ** Pointcut 작성 방법 **
// @Pointcut("execution([접근제한자] 반환형 패키지+클래스명.메서드명([매개변수]))")
// * : 모든 | 아무 값
// .. : 하위 | 아래 (하위 패키지) | 0개 이상의 매개변수
// (모든 반환형 패키지명 아래.Impl로 끝나는 모든 클래스.모든메소드(0개 이상의 매개변수)
@Pointcut("execution(* edu.junsu.comm.member..*Impl.*(..))")
public void memberPointcut() {} // 내용 작성 X
// 직접 "execution(* edu.junsu.comm.member..*Impl.*(..))"로 작성해도 가능
// 메서드를 정해서 불러오기도 가능
@Before("memberPointcut()")
public void start() {
logger.info("--------------Service Start--------------");
}
// 직접 "execution(* edu.junsu.comm.member..*Impl.*(..))"로 작성해도 가능
// 메서드를 정해서 불러오기도 가능
@After("memberPointcut()")
public void end() {
logger.info("--------------Service End--------------");
}
전처리와 후처리를 수행하는 어노테이션 Around 어드바이스는 JoinPoint의 하위 타입인 ProceedingJoinPoint 타입의 파라미터를 필수적으로 선언해야 한다.
@Component // 단순 bean 등록
@Aspect // Aspect임을 알림
@Order(4) // Aspect 순서 지정
public class AroundAspect {
private Logger logger = LoggerFactory.getLogger(AroundAspect.class);
// 사용할 logger 객체 생성
// @Around : 전처리(@Before)와 후처리(@After)를 한 번에 작성 가능한 advice
@Around("memberPointcut()")
public Object runningTime(ProceedingJoinPoint jp) throws Throwable{
// ProceedingJoinPoint를 매개변수로 받아 실행 지점 정보를 가져올 수 있다.
// proceed() 메소드 실행 전 후로 비지니스 메소드의 전 후를 나눈다.
// proceed() 메소드 호출 전 : @Before advice 작성
// proceed() 메소드 호출 후 : @After advice 작성
// 메소드 마지막에 proceed()의 반환값을 리턴해야함
// System.currentTimeMillis():
// 1970/01/01 오천 9시 (한국 OS 기준)부터
// 지금까지 지난 시간을 ms 단위로 나타낸 값
long startMs = System.currentTimeMillis();
Object obj = jp.proceed(); // 전/후 처리를 나누는 기준
long endMs = System.currentTimeMillis();
logger.info("Running Time : " + (endMs - startMs) + "ms");
// 이와 같은 방법으로 서비스 로직의 실행 시간을 알 수 있다.
return obj;
}
}
@After 수행 후에 반환되는 리턴 값을 얻어올 수 있는 어노테이션
@Component
@Aspect
@Order(5) // advice 실행 순서, 클수록 먼저 시작
public class AfterReturningAspect {
private Logger logger = LoggerFactory.getLogger(AfterReturningAspect.class);
// @AfterReturning : 기존 @After + 반환값 얻어오기 기능
@AfterReturning(pointcut="memberPointcut" , returning = "returnObj")
public void serviceReturningValue(JoinPoint jp ,Object returnObj) {
logger.info("Return Value : " + returnObj);
// 반환되는 값을 출력해볼 수 있다.
}
}
@Component
@Aspect
public class AfterThrowingAspect {
private Logger logger = LoggerFactory.getLogger(AfterThrowingAspect.class);
// @AfterThrowing : 기존 @After + 던져지는 예외 얻어오기 기능
@AfterThrowing(pointcut="memberPointcut()" , throwing = "exceptionObj")
public void serviceReturningValue(JoinPoint jp ,Exception exceptionObj) {
// 예외 정보를 Exception 클래스로 생성한다.
String str = "<<exception>> : " + exceptionObj.getStackTrace()[0];
str += " - " + exceptionObj.getMessage() + "\n";
logger.error(str);
}
}