관점 지향 프로그래밍이라고 한다.
비즈니즈로직을 수행하기 위한 여러 모듈들
ex) 데이터베이스 연결, 로깅, 파일 입출력 등
이런게 비즈니스로직마다 따로 박혀있으면 생산성, 유지보수측면에서 너무 안좋다.
뭐하나 바뀔때마다 가서 하나하나 다 바꿔줘야됨
그래서 이걸 비즈니스 로직에서 분리해서 재사용가능하게 모듈화를 하겠다는게 AOP의 목적
- Aspect : 위에서 설명한 흩어진 관심사를 모듈화 한 것. 주로 부가기능을 모듈화함.
- Target : Aspect를 적용하는 곳 (클래스, 메서드 .. )
- Advice : 실질적으로 어떤 일을 해야할 지에 대한 것, 실질적인 부가기능을 담은 구현체
- JointPoint : Advice가 적용될 위치, 끼어들 수 있는 지점. 메서드 진입 지점, 생성자 호출 시점, 필드에서 값을 꺼내올 때 등 다양한 시점에 적용가능
- PointCut : JointPoint의 상세한 스펙을 정의한 것. 'A란 메서드의 진입 시점에 호출할 것'과 같이 더욱 구체적으로 Advice가 실행될 지점을 정할 수 있음
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
@Aspect // aspect라는걸 명시
@Configuration // bean으로 등록
public class UseAccessAspect {
private Logger logger = LoggerFactory.getLogger(this.getClass());
// What kind of method calls I would intercept
// execution(* PACKAGE.*.*(..))
// Weaving & Weaver
// joinpoint 에 들어오는 시점 실행전에 수행
@Before("com.in28minutes.spring.aop.springaop.aspect.CommonJoinPointConfig.dataLayerExecution()")
public void before(JoinPoint joinpoint) {
//Advice
logger.info("Check for user access");
logger.info("Allowed execution for - {}", joinpoint);
}
}
@Aspect
@Configuration
public class AfterAopAspect {
private Logger logger = LoggerFactory.getLogger(this.getClass());
// return 되는 객체 가로챔, 실행이 성공적으로 완료될때만 실행
@AfterReturning(value = "com.in28minutes.spring.aop.springaop.aspect.CommonJoinPointConfig.businessLayerExecution()",
returning = "result")
public void afterReturning(JoinPoint joinpoint, Object result) {
logger.info("{} return with value {}", joinpoint, result);
}
// 예외 발생시에 실행, Exception객체를 해놨으므로 지금은 모든예외 다 적용
@AfterThrowing(value = "com.in28minutes.spring.aop.springaop.aspect.CommonJoinPointConfig.businessLayerExecution()",
throwing = "exception")
public void afterThrowing(JoinPoint joinpoint, Exception exception) {
logger.info("{} return with value {}", joinpoint, exception);
}
// 그냥 joinpoing후에 실행
@After(value = "com.in28minutes.spring.aop.springaop.aspect.CommonJoinPointConfig.businessLayerExecution()")
public void after(JoinPoint joinpoint) {
logger.info("after execution of {}", joinpoint);
}
}
@Aspect
@Configuration
public class MethodExecutionAspect {
private Logger logger = LoggerFactory.getLogger(this.getClass());
@Around("com.in28minutes.spring.aop.springaop.aspect.CommonJoinPointConfig.trackTimeAnnotation()")
public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
// 실행전에 하는거
long startTime = System.currentTimeMillis();
// 분기점, 이게 내가 지정한 메서드가 실행되는 시점
proceedingJoinPoint.proceed();
// 실행후에 하는거
long timeTaken = System.currentTimeMillis() - startTime;
logger.info("Time Taken by {} is {}", proceedingJoinPoint, timeTaken);
}
}
@Before (이전) : 어드바이스 타겟 메소드가 호출되기 전에 어드바이스 기능을 수행
@After (이후) : 타겟 메소드의 결과에 관계없이(즉 성공, 예외 관계없이) 타겟 메소드가 완료 되면 어드바이스 기능을 수행
@AfterReturning (정상적 반환 이후)타겟 메소드가 성공적으로 결과값을 반환 후에 어드바이스 기능을 수행
@AfterThrowing (예외 발생 이후) : 타겟 메소드가 수행 중 예외를 던지게 되면 어드바이스 기능을 수행
@Around (메소드 실행 전후) : 어드바이스가 타겟 메소드를 감싸서 타겟 메소드 호출전과 후에 어드바이스 기능을 수행
public class CommonJoinPointConfig {
// 기본적인 사용법
@Pointcut("execution(* com.in28minutes.spring.aop.springaop.data.*.*(..))")
public void dataLayerExecution() {}
@Pointcut("execution(* com.in28minutes.spring.aop.springaop.business.*.*(..))")
public void businessLayerExecution() {}
// 2개를 묶어서 지정가능
@Pointcut("com.in28minutes.spring.aop.springaop.aspect.CommonJoinPointConfig.dataLayerExecution() "
+ "&& com.in28minutes.spring.aop.springaop.aspect.CommonJoinPointConfig.businessLayerExecution()")
public void allLayerExecution() {}
// name에 dao가 포함되는것들
@Pointcut("bean(*dao*)")
public void beanContainingDao() {}
// 특정 패키지 제외
@Pointcut("within(*com.in28minutes.spring.aop.springaop.data..*)")
public void dataLayerExecutionWithWithin() {}
// 특정 어노테이션이 있는 것들
@Pointcut("@annotation(com.in28minutes.spring.aop.springaop.aspect.TrackTime)")
public void trackTimeAnnotation() {}
}
이런게 없으면 특정 pointcut을 바꾸고 싶다하면 어노테이션을 적용한 모든 메서드에 가서 일일히 바꿔줘야되지만
common class 같은거 하나 만들어놓으면 변경사항이 있다면 여기만 바꿔주면 일괄수정가능 관리가 아주쉽다.