Spring AOP
01 Feb 2018용어 정리
Aspect
여러 곳에서 쓰이는 코드(공통 부분)를 모듈화한 것
Advice + JoinPoint 로 이루어진다.
Target
Aspect가 적용되는 곳
Advice
Aspect에서 실질적인 기능을 구현한 구현체
JoinPoint
Advice가 Target 에 적용되는 시점
메서드 진입할 때, 생성자 호출할 때, 필드에서 값을 꺼낼 때 등등
PointCut
Joint point 의 상세 스펙을 정의한 것
Proxy
타겟을 감싸서 타겟의 요청을 대신 받아주는 랩핑 오브젝트이다. 클라이언트가 타겟을 호출하게 되면 프록시가 호출을 가로채 어드바이스에 등록된 기능을 수행 후 타겟 메소드를 호출한다.
예제
의존성 추가
implementation("org.springframework.boot:spring-boot-starter-aop")
기본 예시
@Aspect
@Component
class Performance {
@Pointcut("execution(* com.board.BoardService.getBoards(..))")
fun getBoards(){}
@Pointcut("execution(* com.user.UserService.getUsers(..))")
fun getUsers(){}
@Around("getBoards() || getUsers()")
fun calculatePerformance(proceedingJoinPoint: ProceedingJoinPoint): Any {
val start = System.currentTimeMillis()
val result = proceedingJoinPoint.proceed()
val end = System.currentTimeMillis()
println("수행 시간 : "+ (end - start))
return result
}
}
Aspect에서 타겟 메서드의 파라미터 사용
@Aspect
@Component
class UserHistory(
private val historyRepository: HistoryRepository
) {
@Pointcut("execution(* com.user.UserService.update(*)) && args(user)")
fun updateUser(user: User){}
@AfterReturning("updateUser(user)")
fun saveHistory(user: User){
historyRepository.save(History(user.getIdx()))
}
}
Annotaion 기반으로 Aspect 적용
애노테이션 생성
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.CLASS)
annotation class PerfLogging
Aspect클래스 포인트컷 변경
@Component
@Aspect
class PerfAspect {
@Around("@annotation(PerfLogging)")
fun logPerf(pjp: ProceedingJoinPoint): Any {
val begin = System.currentTimeMillis()
val result = pjp.proceed()
println(System.currentTimeMillis() - begin)
return result
}
}
Aspect 적용
@Component
class SimpleServiceEvent {
@PerfLogging
fun created() {
...
}
}
주의사항
동일한 클래스의 내부에서 Aspect가 적용된 메서드를 호출할 때는 적용되지 않는다. 따라서 별도의 클래스로 분리해야 한다.
@Component
class SimpleServiceEvent {
// 아래 메서드를 실행할 경우 Aspect가 적용되지 않음
fun callCreated() {
this.creaeted()
}
@PerfLogging
fun created() {
...
}
}
클래스를 분리한다.
@Component
class SimpleServiceEvent(
private val internalSimpleServiceEvent: InternalSimpleServiceEvent
) {
fun callCreated() {
internalSimpleServiceEvent.creaeted()
}
}
@Component
class InternalSimpleServiceEvent {
@PerfLogging
fun created() {
...
}
}
아래 방법 처럼 자신을 주입 받아서 처리할수 있다. (의존성 참조 순환 오류가 발생하는 경우도 있는것 같다)
@Service
class SimpleService {
@Autowired
private lateinit var simpleService: SimpleService
fun outsideMethod(): String {
return simpleService.get()
}
@PerfLogging
fun get(): String {
}
}