skarltjr / Memory_Write_Record

나의 모든 학습 기록
0 stars 0 forks source link

스프링 핵심기술 AOP #69

Open skarltjr opened 2 years ago

skarltjr commented 2 years ago

기본이론

문제 상황

public void timeCheck() {
    Stopwatch stopwatch = Stopwatch.createStarted();
    doSomething();
   stopwatch.stop();
   log.info("time : " + stopwatch.elapsed(MILLISECONDS));
}

public void doSomething() {
    ... 비즈니스 로직이 존재
}
n개 매서드을 위한 시간측정 매서드 n개를 생성
=> 수많은 중복 코드 발생

AOP

AOP 용어 정리

즉 여기서는 시간을 측정할 매서드들 선정

- PointCut :

Advice가 적용될 JoinPoint를 선정하는 방법 모든 Join Point에 Advice를 적용하는 것이 아닌 특정 Join Point에 Advice를 적용하는것이 일반적. 따라서 Advice를 사용할 JoinPoint를 선정하는 방법이 PointCut

즉 여기서는 시간을 측정할 매서드들 선정하는데 어떻게 선정할지 방법을 정의

- Target : Advice가 적용되어지는 기존 메서드, 클래스 등을 뜻
- AOP Proxy : 

Spring에서 AOP는 Dynamic Proxy 기법으로 AOP를 구현 우리가 AOP가 적용된 Target 메서드를 호출 할 때 바로 그 메서드가 호출되는 것이 아니라 Advice가 요청을 대신 랩핑(Wrraping) 클래스로써 받고 그 랩핑 클래스가 Target을 호출

- Weaving : Aspect가 target에 적용되는 전체적인 과정. 
  - 즉, PointCut으로 지정된 JoinPoint에 Advice가 적용되어 Target을 호출 시 AOP Proxy가 만들어지는 과정
![스크린샷 2022-02-09 오후 6 50 20](https://user-images.githubusercontent.com/62214428/153171337-4d6096bb-9e17-4909-81f2-4505f7c24f59.png)

### 위빙의 종류 with aspectJ와 비교
- 위빙이란 JoinPoint에 Advice가 적용되어 Target을 호출 시 타겟매서드를 대체할 AOP Proxy객체가 만들어지는 과정

spring aop VS AspectJ

중요한건 AOP라는 개념은 스프링이 만들어낸 개념이아니고 스프링은 단지 자신들만의 방식으로 AOP를 실현하여 보다 손쉽게 활용할 수 있도록 해주는것 특히 스프링의 경우 스프링빈을 대상으로만 작동하는 반면 aspectJ는 모든것이 대상이될 수 있다. 또한 위빙 방식에서 차이를 보인다.

ex) articleService를 대신할 $$EnhancerBySpringCGLIB$$~와 같은 이름을 가진 가짜객체가 생성되는 과정, 방식

- RuntimeWeaving

장점 : 따라서 설정이 쉬우며 기존 나의 소스코드 변경없이 적용이 가능 ex) transaction 어노테이션 사용처럼 단점 : 동일빈에서 호출시 동작하지 않는 문제가 있으며 dynamic proxy의 경우 인터페이스가 강제되고 cglib의 경우 private 접근 제한자에 대해서 사용 불가 추가로.. 말 그대로 런타임시점에 위빙이 동작하기에 비교적 느리다


- load-time weaving
skarltjr commented 2 years ago

실전

AOP적용 타입

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.

Spring Aspect

  1. 의존성 추가
    implementation 'org.springframework.boot:spring-boot-starter-aop'
  2. 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() {

}

advice

@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">
skarltjr commented 2 years ago

결과적으로 동일한 로직(시간측정)이 중복된 코드없이 다양한곳에서 실행될 수 있었따. 바꿔 말하면 하나의 시간측정 로직이 존재하고 이를 적용할 대상만 갈아끼워 여러곳에서 시간을 측정할 수 있는 효과를 얻었다.