오늘은 AOP 맛보기 2강을 본다는 걸 핑계로, eclipse 재단의 aspectj 에 대한 근본적인 이해를 시도했습니다. 무엇보다도 AOP (1) 강의에서 나왔던 Pointcut 지점의 표현식이 완전 블랙박스여서 나중에 활용하려고 하더라도 전혀 쓸 수 없는 상태이기 때문에 언젠가 한 번은 알아뒀어야 할 부분이니, 나온 김에 배워두고 가는 게 좋겠는 생각이 들더라구요.
kotlin 으로 하는 실습은 덤이구요. 뭐 별 차이는 없드라구요 ㅋㅋ
Eclipse Aspectj
- 누가 만들었는가? : PARC (캘리포니아 연구 기업)
- 누가 만들고 있는가: Eclipse
- 홈페이지: https://www.eclipse.org/aspectj
- 문서 포탈: https://www.eclipse.org/aspectj/docs.php
- Getting Started: https://www.eclipse.org/aspectj/doc/released/progguide/starting-aspectj.html
- AspectJ Language (Pointcut 등에 대한 자세한 설명):
https://www.eclipse.org/aspectj/doc/released/progguide/language.html - 이상하게 스프링 관련 글을 검색하면 공식 홈페이지보단 블로그가 먼저 나오더라
블로그 글 작성자를 믿어요? 전적으로 신뢰할 수 있어요? 전 아닌데요. 도움이 되는 건 사실이지만...
검색을 해보니 스프링에는 aspectj 말고도 스프링 전용의 좀 더 단순한 Spring AOP 도 있는 모양입니다. 그 쪽은 이번엔 제쳐두고.
starting aspectj 만 읽어보겠습니다. 이 이상으로 들어가는 건 불필요해보이네요.
Aspectj: Why
- crosscutting concern 은 객체지향으로 담기에 어려워서.
- -> 여러 클래스 / 메소드에 걸쳐 실행될 내용을, "객체 단위로" 기술하는 방법을 제시
Aspectj: 핵심 개념
- Joint Point: a well-defined point in the program flow
잘 정의된 프로그램의 한 지점, 그러니까 훅을 걸 프로그램의 실제 지점을 말합니다. - Pointcut: picks out certain join points and values at those points
특정 Joint Point 와 그 지점에 있는 값들을 잡아챕니다. 훅을 걸 지점을 정의한 걸 말하는 모양이네요. - Advice: is code that is executed when a join point is reached (These are the dynamic parts of AspectJ)
Pointcut 에서 정의했던 Joint Point 에 도달하면 실행할 함수들입니다. 동적인 부분이라는 설명이 있던데 잘 이해가 안 되니 일단 패스
Aspectj: 구성요소:
Pointcut - 낚아챌 지점 정의
Pointcut 문법은 몇 개 예시를 보면 금방 알 것 같이 생겼습니다. (문서에서 챙겨왔음)
// 단일 call Pointcut 정의는 생략 (밑에 보세요)
// 여러 Pointcut의 묶음으로 정의도 가능
pointcut move():
call(void FigureElement.setXY(int,int)) ||
call(void Point.setX(int))
;
// 와일드카드도 가능
call(void Figure.make*(..))
call(public * Figure.* (..))
// 다른 Pointcut 을 대상으로 잡을수도 있음
cflow(move())
fastcampus 강의에서는 execution 포인트를 지정했지만, 시작하기 문서에는 call 만 소개합니다. 둘의 차이점은 문서에 있네요.
https://www.eclipse.org/aspectj/doc/released/progguide/language-joinPoints.html#call-vs-execution
이 둘은 실행 시점이 미묘하게 다르므로, Advice에서 잡을 수 있는 문맥이 다릅니다.
- call: 함수의 본문이 아직 실행되기 이전의 시점에 낚아챕니다.
- execute: 함수의 본문이 이미 실행된 다음에 낚아챕니다.
이것만으로는 실사용시의 차이점을 잘 모르겠지만 지금은 저 둘의 차이점이 중요하지 않은 상황이므로 건너뛰겠습니다. 필요해질 때 보면 되겠죠!
Aspectj: 구성요소:
Advice - 낚아챘을 때 실행할 함수
좀 많습니다.
- before
- after
- 그냥 after (finally 와 같음)
- after returning
- after throwing
- around: 튜토리얼에서 설명하지 않습니다. 호출 여부 자체를 막을 수 있다는걸로 봐서 manual call visitor 처럼 직접 호출하는 구조인가봅니다.
지점을 선언하는 방법, 패러미터를 정의하는 방법 (다른 pointcut 인 this / target / args 를 활용해서 값을 가져오는 방법) 등이 설명이 되어있는데, 아마 어노테이션으로 하는 거랑 문법이나 방법이 좀 다를겁니다. 일단 참고로만 기록을 해두겠습니다. 자세한 건 문서를 확인.
after(FigureElement fe, int x, int y) returning:
call(void FigureElement.setXY(int, int))
&& target(fe)
&& args(x, y) {
System.out.println(fe + " moved to (" + x + ", " + y + ")");
}
Aspectj: 구성요소:
Inter-type declaration: 정적 컴파일되는 AOP Interface Mixin
컴파일타임에 클래스 계층구조를 바꾸거나 인터페이스를 추가하거나 한답니다. 인터페이스를 만들고 그 구현을 상속받는 방식이라나요? 생긴게 딱 Mixin 같습니다.
아무튼 지금은 중요하지 않을 것 같습니다. 예시도 생략.
AOP 1강의 블랙박스: Aspect 와일드카드 문법
@Pointcut("execution(* com.example.aop.controller..*.*(..))")
https://www.eclipse.org/aspectj/doc/released/progguide/quick-typePatterns.html
- * 기호: 하위 패키지까지는 매칭하지 않습니다.
An embedded * in an identifier matches any sequence of characters, but does not match the package (or inner-type) separator ".". - .. 기호: 패키지 구분자 사이의 문자열을 모두 매칭합니다.
An embedded .. in an identifier matches any sequence of characters that starts and ends with the package (or inner-type) separator ".".
대체 뭔 차이야...???
https://stackoverflow.com/a/12303934
* 은 요소 하나고 .. 은 그 사이 죄다래요. 그럼 저 Pointcut 정의는 ..* 로 끝났어도 아무 문제가 없었을 내용이네요...
직접 해보자: kotlin REST
아무리 해도 JSON 이 반환이 안 되더라구요. 아무 생각 없이 Around 어드바이스를 넣어서 그랬습니다. ResponseEntity<>는 필요없었지만 그것도 까먹었고 말이죠
잘 돌아가네요. 남은건 강의를 마저 보는 것 뿐입니다...
@RestController
@RequestMapping("/api/greeting")
class HelloApi {
@GetMapping("/{name}")
fun greeting(@PathVariable name: String): String {
return "Hello, ${name}!"
}
@PostMapping
fun addGreeting(@RequestBody user: User): User {
return user
// return ResponseEntity.ok(user)
}
}
@Component
@Aspect
class AspectLogger {
@Pointcut("execution(* com.fastcampus.aop_demo..* (..) )")
private fun cut() {}
@Before("cut()")
fun before_cut() {
println("before_cut")
}
}
AOP "실무 사례" (1)
이제 joinPoint가 뭔지 그 의미를 아니까 헤매일 이유가 없습니다.
@Component
@Aspect
class AspectLogger {
@Pointcut("execution(* com.fastcampus.aop_demo..* (..) )")
private fun cut() {}
@Before("cut()")
fun before_cut() {
println("before_cut")
}
@AfterReturning("cut()", returning = "returnObj")
fun cut_returning(joinPoint: JoinPoint, returnObj: Any) {
println("class:: ${joinPoint.target.javaClass.simpleName}")
println("method:: ${(joinPoint.signature as? MethodSignature)?.method?.name}")
val params = joinPoint.args?.map {
"type ${it.javaClass.simpleName} / value $it"
}?.joinToString()
println("args:: ${params ?: "(none)"}")
println("return:: ${returnObj.javaClass.simpleName}")
println(returnObj)
}
}
AOP "실무 사례" (2) - 커스텀 어노테이션 만들기
어노테이션 만들기
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Timer {}
kotlin 어노테이션 만들기 - java 어노테이션과 호환된다고 하네요.
https://kotlinlang.org/docs/annotations.html
@Target(AnnotationTarget.TYPE, AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
annotation class Timer
@Retention: 어노테이션을 어디에 저장할지 결정할 수 있습니다. SOURCE: 소스에만, BINARY: 바이너리에도, RUNTIME: 바이너리 뿐만이 아니라 런타임에까지 유지 (디폴트)
@Aspect
@Component
class TimerLogger {
@Pointcut("execution( * com.fastcampus.aop_demo..*(..) )")
private fun cutAll() = Unit
@Pointcut("@annotation(com.fastcampus.aop_demo.cross.Timer)")
private fun cutTimer() = Unit
@Around("cutAll() && cutTimer()")
fun around(jointPoint: ProceedingJoinPoint) {
val watch = StopWatch()
watch.start()
val time = measureTimeMillis {
val result = jointPoint.proceed()
}
watch.stop()
println("time: $time")
println("time (spring): ${watch.totalTimeMillis}")
}
}
타이머는 스프링에도 있고 기존에 알고있던 kotlin의 것도 있습니다. 어노테이션을 만드는 거부터가 신기한데 이미 AOP가 있으니까 거기에서 사용하는 것도 놀랍네요.
AOP "실무 사례" (2) - 값 변환하기
예시로, 특정 외부기관이 암호화를 해서 보내주는 경우에 어노테이션만으로 적용할 수 있게 만드는 게 나왔습니다. 의외로 @Before, @After 에서도 가능하네요. 역시 jointPoint 를 사용해서 호출 전/후에 처리합니다. execution 포인트컷인 건 동일하구요.
user.email 이 base64로 인코딩되어있다고 가정하네요. java.utils.Base64 에 내장 기능으로 있었군요
이 부분은 지금은 실습할 필요가 없을 것 같습니다. 나중에 필요할 때 보면 되겠죠. 노트에만 정리해야겠네요.
본 포스팅은 패스트캠퍼스 환급 챌린지 참여를 위해 작성되었습니다.
패스트캠퍼스: https://bit.ly/37BpXiC
#패스트캠퍼스 #패캠챌린지 #직장인인강 #직장인자기계발 #패스트캠퍼스후기 #한번에끝내는JavaSpring웹개발마스터초격차패키지Online
'FastCampus - 한번에 끝내는 Java|Spring 웹 개발 > 03 스프링 입문' 카테고리의 다른 글
스프링 Ch 6-1 validation - 패스트캠퍼스 챌린지 8일차 (0) | 2022.01.31 |
---|---|
스프링 Ch 5 OM/Annotation 정리 - 패스트캠퍼스 챌린지 7일차 (0) | 2022.01.30 |
스프링 Ch 5 스프링 좀 더 - AOP (1) - 패스트캠퍼스 챌린지 5일차 (0) | 2022.01.28 |
스프링 Ch 5 스프링 좀 더 - IoC, DI - 패스트캠퍼스 챌린지 4일차 (0) | 2022.01.27 |
스프링 Ch 04 스프링 부트 시작하기 - 패스트캠퍼스 챌린지 3일차 (0) | 2022.01.26 |