Open skarltjr opened 2 years ago
1.
@Before
Before는 target 메서드가 실행되기 전에 Advice가 실행.
target이 실행되지 못하도록 막는 방법은 가지고 있지 않음. (exception을 발생시키면 되기는 한다고 함.)
2.
@After
after는 target매서드가 실행된 후 advice가 실행
상적으로 메서드가 마무리되든 비정상적으로 exception이 발생하든 무조건 실행되는 Aspect
3.
@Around
Around는 traget 메서드를 감싸는 Advice.
즉, 앞과 뒤에 모두 영향을 미칠 수 있다.
또한 Around는 target을 실행할 지 아니면 바로 반환할지도 정할 수 있다
4. etc
@AfterReturning
AfterReturning은 target 메서드가 정상적으로 끝낫을 경우 실행되는 Advice.
@AfterThrowing
AfterThrowing은 target 메서드에서 throwing이 발생했을 때 실행되는 Advice.
implementation 'org.springframework.boot:spring-boot-starter-aop'
Spring Aspect활성화
@SpringBootApplication
@EnableAspectJAutoProxy
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
3. target선언
advice를 적용받을 타겟 매서드를 선언
@Component public class Target { public void doSomeThing() { System.out.println("do some things"); }
public void doSomeThing2() {
for (int i = 0; i < 30000; i++) {
a++;
}
System.out.println("do some things 2");
}
}
4. aspect 선언
@Aspect @Component public class TimeCheckAspect {
}
5. point cut 선언
@Aspect @Component public class TimeCheckAspect {
@Pointcut(("execution(public * com.example.demo.*.*(..))"))
private void timeCheckPointCut() {
}
}
6. advice구성
@Aspect @Component public class TimeCheckAspect {
@Pointcut(("execution(public * com.example.demo.*.*(..))"))
private void timeCheckPointCut() {
}
@Around("com.example.demo.TimeCheckAspect.timeCheckPointCut()")
public Object timeCheck(ProceedingJoinPoint joinPoint) throws Throwable {
StopWatch stopwatch = new StopWatch();
stopwatch.start();
Object proceed = joinPoint.proceed(); // target method 실행
stopwatch.stop();
System.out.println("time : " + stopwatch.getTotalTimeMillis());
return proceed;
}
}
7. 테스트
- 참고로 5번 pointCut선언에서 모든 매서드에 대해서 수행한다고 지정
@RestController public class Controller { private Target target;
public Controller(Target target) {
this.target = target;
}
@GetMapping("/")
public void hello() {
target.doSomeThing();
}
@GetMapping("/2")
public void hello2() {
target.doSomeThing2();
}
}
<img width="179" alt="스크린샷 2022-02-09 오후 7 41 40" src="https://user-images.githubusercontent.com/62214428/153181889-6d5c6593-fc53-4eea-abba-e0b4c3552d21.png">
결과적으로 동일한 로직(시간측정)이 중복된 코드없이 다양한곳에서 실행될 수 있었따. 바꿔 말하면 하나의 시간측정 로직이 존재하고 이를 적용할 대상만 갈아끼워 여러곳에서 시간을 측정할 수 있는 효과를 얻었다.
기본이론
문제 상황
AOP
AOP 용어 정리
Aspect :
Advice : AOP에서 실제 실행되는 코드를 이야기합니다. 순수하게 실행되는 코드
Join Point :
즉 여기서는 시간을 측정할 매서드들 선정
Advice가 적용될 JoinPoint를 선정하는 방법 모든 Join Point에 Advice를 적용하는 것이 아닌 특정 Join Point에 Advice를 적용하는것이 일반적. 따라서 Advice를 사용할 JoinPoint를 선정하는 방법이 PointCut
즉 여기서는 시간을 측정할 매서드들 선정하는데 어떻게 선정할지 방법을 정의
Spring에서 AOP는 Dynamic Proxy 기법으로 AOP를 구현 우리가 AOP가 적용된 Target 메서드를 호출 할 때 바로 그 메서드가 호출되는 것이 아니라 Advice가 요청을 대신 랩핑(Wrraping) 클래스로써 받고 그 랩핑 클래스가 Target을 호출
spring aop VS AspectJ
중요한건 AOP라는 개념은 스프링이 만들어낸 개념이아니고 스프링은 단지 자신들만의 방식으로 AOP를 실현하여 보다 손쉽게 활용할 수 있도록 해주는것 특히 스프링의 경우 스프링빈을 대상으로만 작동하는 반면 aspectJ는 모든것이 대상이될 수 있다. 또한 위빙 방식에서 차이를 보인다.
ex) articleService를 대신할 $$EnhancerBySpringCGLIB$$~와 같은 이름을 가진 가짜객체가 생성되는 과정, 방식
장점 : 따라서 설정이 쉬우며 기존 나의 소스코드 변경없이 적용이 가능 ex) transaction 어노테이션 사용처럼 단점 : 동일빈에서 호출시 동작하지 않는 문제가 있으며 dynamic proxy의 경우 인터페이스가 강제되고 cglib의 경우 private 접근 제한자에 대해서 사용 불가 추가로.. 말 그대로 런타임시점에 위빙이 동작하기에 비교적 느리다
spring aop가 아닌 AspectJ 라이브러리를 활용하는 방법
즉 추가적인 의존성이 필요
클래스로더가 jvm에 객체를 로딩하는 시점에 AspectJ의 weaver가 모든 객체를 다 불러온 후 바이트 코드를 조작하여 가짜 객체를 만든 후 이 가짜객체도 jvm에게 넘겨주는 방식
컴파일 시점에는 컴파일타임 위빙보다 속도가 빠르지만 런타임시간에는 컴파일타임 위빙보다 느린편이다.
compile-time weaving
마찬가지로 spring aop가 아닌 AspectJ 라이브러리를 활용하는 방법
컴파일 시점에 AspectrJ 컴파일러가 바이트코드를 생성하는데 이때 바이트코드를 조작하여 advice 코드를 삽입
ex) 컴파일 시점에 어떤 객체 앞뒤로 시간을 측정하는 코드를 삽입
가장 빠른 성능을 보여주지만 바이트코드를 조작하는 lombok과 같은 라이브러리와의 충돌이 있을 수 있다.