이전까지, 빈 후처리기와 @Aspect Annotation을 사용해서 로직을 분기 처리하고, 자동 생성, 수정 되도록 만들었습니다.
이전 글
관점
을 하나하나의 기능에서, 관심사 관점
으로 보는 것 입니다.@Aspect
가 이러한 기능을 제공하는 Annotation 입니다.LoggerAdvice
, LoggerAdviceAspect
또한, 기능(Advice
), 적용대상(Pointcut
)을 포함하고 있어, 하나의 Aspect 라고 볼 수 있습니다.Spring
에서는 AOP를 구현하기 위한 프레임워크로 AspectJ
를 차용해서 일부 적용 하였습니다.Spring
에서 채용한 AspectJ 프레임워크
는, 다음과 같은 특징을 가집니다..java
소스 코드를 .class
파일로 만드는 컴파일 과정에서, 컴파일러가 기능 로직을 추가하는 것 입니다..class
를 디컴파일
해보면, 기능 코드가 실제 코드에 추가되는 모습을 볼 수 있습니다.위빙 (Weaving)
이라고 합니다..class
파일을 목적에 따라 메모리에 업로드 하게 됩니다..class
를 조작해서 업로드하게 됩니다.프록시
, DI
, 빈 후처리기
같은 개념들을 총 집합해서 적용 합니다.조인 포인트
(Join point
)포인트 컷
(Pointcut
)타겟
(Target
)어드바이스
(Advice
)애스팩트
(Aspect
)어드바이저
(Advisor
)위빙
예제에 사용한 코드 : Github
예제에 사용한 코드 : Github
baekgwa.springaop.global.aop.AspectV1 참조
@Aspect
@Slf4j
public class AspectV1 {
@Around("execution(* baekgwa.springaop.web..*(..))")
public Object doLog(ProceedingJoinPoint joinPoint) throws Throwable {
log.info("[START] : {}", joinPoint.getSignature());
Object result = joinPoint.proceed();
log.info("[E N D] : {}", joinPoint.getSignature());
return result;
}
}
@Aspect
를 클래스에 추가해주고, @Around
를 통해 pointcut
을 선정해 줍니다.ProceedingJoinPoint
Interface를 받아와야 합니다.Signature()
, 메타데이터 정보
등등을 받을 수 있고, proceed()
를 통해 타겟 클래스의 타켓 매서드를 실행할 수 있습니다.결과 확인해보기
@Around
에 포인트컷을 지정해도 되지만, 포인트 컷을 분리해서 따로 사용할 수도 있습니다.재활용성
을 극대화 시켜, 모듈화
를 이뤄낼 수 있습니다.public class WebPointcut {
@Pointcut("execution(* baekgwa.springaop.web..*(..))")
public void allMethod() {}
}
@Aspect
@Slf4j
public class AspectV1 {
//case1) 직접 pointcut 작성하여 적용
// @Around("execution(* baekgwa.springaop.web..*(..))")
//case2) 외부에서 pointcut 만들어서 주입. 재활용 가능한 장점이 있음. (모듈화)
@Around("baekgwa.springaop.global.aop.pointcuts.WebPointcut.allMethod()")
public Object doLog(ProceedingJoinPoint joinPoint) throws Throwable {
log.info("[START] : {}", joinPoint.getSignature());
Object result = joinPoint.proceed();
log.info("[E N D] : {}", joinPoint.getSignature());
return result;
}
}
void
타입으로 반환해야 합니다.예제에 사용한 코드 : Github
baekgwa.springaop.global.aop.AspectV2 참조
public class WebPointcut {
@Pointcut("execution(* baekgwa.springaop.web..*(..))")
public void allMethod() {}
@Pointcut("execution(* baekgwa.springaop.web..*Service*.*(..))")
public void transactionOnlyService() {}
}
&&
연산으로 처리하여 둘다 참일때 실행되도록 하였습니다.~~~기존코드~~~
@Around("baekgwa.springaop.global.aop.pointcuts.WebPointcut.allMethod() &&"
+ "baekgwa.springaop.global.aop.pointcuts.WebPointcut.transactionOnlyService()")
public Object transaction(ProceedingJoinPoint joinPoint) throws Throwable {
try {
log.info("[Transaction Start] : {}", joinPoint.getSignature());
Object result = joinPoint.proceed();
log.info("[Transaction Commit] : {}", joinPoint.getSignature());
return result;
} catch (IllegalStateException e) {
log.info("[Transaction Rollback] : {}", joinPoint.getSignature());
throw e;
} finally {
log.info("[Transaction Resource Clear] : {}", joinPoint.getSignature());
}
}
}
결과 확인해보기
- [Post] http://localhost:8080/cart/items
- [Post] Exception 발생!! http://localhost:8080/cart/items
예제에 사용한 코드 : Github
baekgwa.springaop.global.aop.AspectV3 참조
@Order
Annotation을 사용하면 됩니다!@Aspect
@Slf4j
public class AspectV3 {
@Order(1)
@Around("baekgwa.springaop.global.aop.pointcuts.WebPointcut.allMethod()")
public Object doLog(ProceedingJoinPoint joinPoint) throws Throwable {
~~~
}
@Order(2)
@Around("baekgwa.springaop.global.aop.pointcuts.WebPointcut.allMethod() &&"
+ "baekgwa.springaop.global.aop.pointcuts.WebPointcut.transactionOnlyService()")
public Object transaction(ProceedingJoinPoint joinPoint) throws Throwable {
~~~
}
}
Order
는, 클래스에 붙였을 때 유효하게 되어있기 때문입니다.public class AspectV3 {
@Aspect
@Slf4j
@Order(1)
public static class DoLog {
@Around("baekgwa.springaop.global.aop.pointcuts.WebPointcut.allMethod()")
public Object doLog(ProceedingJoinPoint joinPoint) throws Throwable {
log.info("[START] : {}", joinPoint.getSignature());
Object result = joinPoint.proceed();
log.info("[E N D] : {}", joinPoint.getSignature());
return result;
}
}
@Aspect
@Slf4j
@Order(2)
public static class Transaction {
@Around("baekgwa.springaop.global.aop.pointcuts.WebPointcut.allMethod() &&"
+ "baekgwa.springaop.global.aop.pointcuts.WebPointcut.transactionOnlyService()")
public Object transaction(ProceedingJoinPoint joinPoint) throws Throwable {
try {
log.info("[Transaction Start] : {}", joinPoint.getSignature());
Object result = joinPoint.proceed();
log.info("[Transaction Commit] : {}", joinPoint.getSignature());
return result;
} catch (IllegalStateException e) {
log.info("[Transaction Rollback] : {}", joinPoint.getSignature());
throw e;
} finally {
log.info("[Transaction Resource Clear] : {}", joinPoint.getSignature());
}
}
}
}
@SpringBootApplication
public class SpringAopApplication {
public static void main(String[] args) {
SpringApplication.run(SpringAopApplication.class, args);
}
~~기존 코드~~
@Bean
public AspectV3.DoLog doLog() {
return new AspectV3.DoLog();
}
@Bean
public AspectV3.Transaction transaction() {
return new AspectV3.Transaction();
}
}
결과 확인해보기
Complete!
고생하셨습니다! 모든 요구사항을 통과한 아름다운 AOP 기술을 적용 하였습니다.
@Around
를 통해 구현을 해야될까요?Advice
가 존재 합니다. 나열해보겟습니다.@Around
: 타겟 호출 전, 후, 조인 포인트 실행 여부, 반환 값 반환, 예외 처리 등 모든것이 가능@Before
: 조인 포인트 실행 전@AfterReturning
: 조인 포인트 실행 후, 정상 완료일 시@AfterThrowing
: 조인 포인트 실행 후, 예외 발생 시@After
: 조인 포인트 실행 후. @AfterReturning
+ @AfterThrowing
AspectV3.Transaction
의 코드를 각각의 영역으로 나누어 주석처리를 통해 확인해 보겠습니다.@Aspect
@Slf4j
@Order(2)
public static class Transaction {
@Around("baekgwa.springaop.global.aop.pointcuts.WebPointcut.allMethod() &&"
+ "baekgwa.springaop.global.aop.pointcuts.WebPointcut.transactionOnlyService()")
public Object transaction(ProceedingJoinPoint joinPoint) throws Throwable {
try {
log.info("[Transaction Start] : {}", joinPoint.getSignature()); //@Before()
Object result = joinPoint.proceed(); //JoinPoint
log.info("[Transaction Commit] : {}", joinPoint.getSignature()); //@AfterReturning()
return result;
} catch (IllegalStateException e) {
log.info("[Transaction Rollback] : {}", joinPoint.getSignature()); //@AfterThrowing()
throw e;
} finally {
log.info("[Transaction Resource Clear] : {}", joinPoint.getSignature()); //@After()
}
}
}
public class AspectV4 {
@Aspect
@Slf4j
@Order(2)
public static class Transaction {
@Before("baekgwa.springaop.global.aop.pointcuts.WebPointcut.allMethod() &&"
+ "baekgwa.springaop.global.aop.pointcuts.WebPointcut.transactionOnlyService()")
public void doTransactionBefore(JoinPoint joinPoint){
log.info("[Transaction Start] : {}", joinPoint.getSignature());
}
@AfterReturning(value = "baekgwa.springaop.global.aop.pointcuts.WebPointcut.allMethod() &&"
+ "baekgwa.springaop.global.aop.pointcuts.WebPointcut.transactionOnlyService()", returning = "result")
public void doTransactionAfterReturning(JoinPoint joinPoint, Object result){
log.info("[Transaction Commit] : {}", joinPoint.getSignature()); //@AfterReturning()
log.info("[AfterReturning Result] = {}", result);
}
@AfterThrowing(value = "baekgwa.springaop.global.aop.pointcuts.WebPointcut.allMethod() &&"
+ "baekgwa.springaop.global.aop.pointcuts.WebPointcut.transactionOnlyService()", throwing = "ex")
public void doTransactionAfterThrowing(JoinPoint joinPoint, Exception ex){
log.info("[Transaction Rollback] : {}", joinPoint.getSignature());
log.info("[AfterThrowing Result] = {}", ex.getMessage());
}
@After("baekgwa.springaop.global.aop.pointcuts.WebPointcut.allMethod() &&"
+ "baekgwa.springaop.global.aop.pointcuts.WebPointcut.transactionOnlyService()")
public void doTransactionAfter(JoinPoint joinPoint) {
log.info("[Transaction Resource Clear] : {}", joinPoint.getSignature());
}
}
}
결과 확인해보기
- [Post] http://localhost:8080/cart/items
- [Post] Exception 발생
@Around
를 사용하면 모든 처리가 가능합니다. 그것도 하나로 간편하게 처리가 가능합니다.@Around("baekgwa.springaop.global.aop.pointcuts.WebPointcut.allMethod() &&"
+ "baekgwa.springaop.global.aop.pointcuts.WebPointcut.transactionOnlyService()")
public Object transaction(ProceedingJoinPoint joinPoint) throws Throwable {
log.info("[Transaction Start] : {}", joinPoint.getSignature());
}
}
}