Spring AOP에 대해 아라보자! - 2부

probsno·2021년 4월 21일
1

SpringCore

목록 보기
9/9

Spring AOP - 프록시 기반 AOP

1부에서 설명했듯이, Spring AOP는 runtime weaving으로 AOP를 구현하기 때문에 프록시를 만들어서 AOP를 구현한다.
스프링 빈을 생성할때 프록시도 생성하기 때문에 스프링 빈으로 등록이 되어 있어야지만 AOP를 쓸수 있음

그럼 프록시 패던은 써서 AOP를 구현하는 이유는?
기존 코드를 변경하지 않고 프록시에다가 코드를 추가함으로써 AOP를 구현 할 수 있기 때문에

여기서 잠깐 프록시 패턴 좀 보구 가실게여~

그림에서 보듯이 프록시 패턴은 interface를 상속받은 진짜 객체와 프록시 객체가 있는데, 진짜 객체는 로직을 구현하고 프록시 객체는 진짜 객체를 불러와서 사용한다. 음... 프록시 패턴으로 구현된 AOP를 한번 봅세당~

프록시 패턴으로 직접 구현한 AOP

//인터페이스
public interface EventService {

   void createEvent();

   void publishEvent();

   void deleteEvent();

}
//진짜 객체 - 실제 메서드의 로직이 구현된 객체임
@Component
public class SimpleEventService implements EventService{
   @Override
   public void createEvent() {
       try{
           Thread.sleep(1000);
       } catch (InterruptedException e) {
           e.printStackTrace();
       }
       System.out.println("Event created");
   }

   @Override
   public void publishEvent() {
       try{
           Thread.sleep(2000);
       }catch(InterruptedException e){
           e.printStackTrace();
       }

       System.out.println("published an event");
   }

   @Override
   public void deleteEvent(){
       System.out.println("event deleted");
   }
}
//프록시 객체 - 실제 로직은 진짜객체에서 불러와서 사용하고 부가적인 부분을 넣어줌
@Primary//빈주입할때 프록시 객체가 주입되도록 @primary어노테이션을 붙여줌
@Component
public class proxyEventService implements EventService{

   @Autowired
   SimpleEventService simpleEventService;

   @Override
   public void createEvent() {
       Long begin = System.currentTimeMillis(); //부가적인 기능 - 메서드 실행 기산을 측정
       simpleEventService.createEvent(); //진짜 객체에서 로직을 불러와서 사용
       System.out.println(System.currentTimeMillis() - begin);//부가적인 기능 - 메서드 실행 기산을 측정
   }

   @Override
   public void publishEvent() {
       Long begin = System.currentTimeMillis();//부가적인 기능 - 메서드 실행 기산을 측정
       simpleEventService.publishEvent(); //진짜 객체에서 로직을 불러와서 사용
       System.out.println(System.currentTimeMillis() - begin);//부가적인 기능 - 메서드 실행 기산을 측정
   }

   @Override
   public void deleteEvent() {
       simpleEventService.deleteEvent();
   }
}
//클라이언트에서 사용할때
@Component
public class AppRunner implements ApplicationRunner {

   @Autowired //이때 주입되는 객체는 프록시 객체이다
   EventService eventService;

   @Override
   public void run(ApplicationArguments args) throws Exception {
       eventService.createEvent();
       eventService.publishEvent();
       eventService.deleteEvent();
   }
}

코드에서 보듯이 구현은 진자 객체에서 한다. 프록시는 부가적인 기능을 추가해서 AOP를 구현한다. 코드에서는 진짜 객체에 수정이 없이도 부가적인 기능을 추가 했다.

Spring AOP에서는 어떻게 구현하는거지?

그건 바로 BeanPostProcessor를 상속받은 AbstractAutoProxyCreator로 프록시를 만들어서 구현한다. 이를 동적 프록시(Dynamic proxy)라구 한다.

  1. 스프링 빈을 읽어들임
  2. 스프링 빈을 생성함
  3. BeanPostProcessor로 빈이 생성되자 마자 실행이 되는 life cycle 메서드임
  4. BeanPostProcessor를 상속받은 AbstractAutoProxyCreator로 빈이 생성되고 나서 바로 프록시를 만듬

Spring에서 동적 프록시를 이용해 AOP를 적용하는 방법

AOP - Advice정의

Advicejoin point시에 적용할 동작을 정의하는 것임. Advice를 만들기 위해서는

1. 동작을 적용할 클래스를 만듬
2. 해당 클래스는 빈으로 등록되어 있어야 함
3. @Aspect어노테이션을 클래스에 붙여줌
4. Advice로 정의할 메서드에 어노테이션(e.g @Around, @Before 등등)

코드로 한번 살펴봅세당~

@Component
@Aspect
public class PerfAspect {

    //@Around("execution(* com.example.*.EventService.*(..))")
    //@Around("bean(simpleEventService)") //해당 빈의 모든 클레스에 적용 (bean id)
    @Around(" @annotation(PerfLoggin)")
    public Object logPerf(ProceedingJoinPoint pip) throws Throwable {
        Long begin = System.currentTimeMillis();
        Object retVal = pip.proceed();
        System.out.println(System.currentTimeMillis() - begin);
        return retVal;
    }
}
  • @Component - 컴포넌트 스캔으로 빈으로 등록

  • @Aspect - 어노테이션을 클래스에 붙여줌

  • @Around()/@Before()/@After() - 등등 advice에 join point 를 지정할때 사용하는 어노테이션

    • @Around - 적용할 point cut 메서드의 앞, 뒤에 advice를 추가할때 씀
    • @After - 결과에 상관 없이, 적용항 point cut 메서드가 실행되기 전에 동작을 정의할때 씀
    • @Before - 결과에 상관 없이, 적용할 point cut 메서드 실행되고 난뒤에 동작을 정의할때 씀
    • @AfterReturning - 메서드 결과가 성공해서 결과를 반환하고 나서 실행하고싶을 때 씀
    • @AfterThrowing - 메서드 결과가 실패했을때 적용하고 싶을 때 씀

    어떤 클래스에 어느 매서드에 적용할 지는 어떻게 정의하지?

    3가지 방법이 있는데 보편적으로 가장 많이 쓰는 방법은 어노테이션을 이용해서 적용하는 방법이다

  • 어노테이션 사용해서 적용하기

  • 패키지 경로를 이용해서 정의하기

  • bean id를 이용해서 정의하기

    1. 어노테이션으로 적용하기
    1.1 어노테이션 만들기
    1.2 어노테이션 적용하기
    1.3 advice의 어노테이션에다가 사용할 어노테이션 정의하기 e.g. @Around("@Annotation(PerfLoggin)")

어노테이션 만들기

//예를들어 Runtime시에도 가지고 있고 싶다면 RetentionPolicy.RUNTIME
//소스상에서만 가지고 있고 .class 파일로 변환할땐 없애고 싶다면 RetentionPolicy.SOURCE
//런타임까지는 유지할 필요 없지만 .class파일에 변환시에도 가지고 있고 싶다면 RetentionPolicy.CLASS
@Retention(RetentionPolicy.CLASS) //어노테이션 정보를 어디까지 유지할지
@Target(ElementType.METHOD)//메서드에 적용할 거라면 ElementType.METHOD
public @interface PerfLoggin {
}

어노테이션 적용하기

@Component
public class SimpleEventService implements EventService{
    @PerfLoggin
    @Override
    public void createEvent() {
        try{
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Event created");
    }

    @PerfLoggin
    @Override
    public void publishEvent() {
        try{
            Thread.sleep(2000);
        }catch(InterruptedException e){
            e.printStackTrace();
        }
        System.out.println("published an event");
    }
}

advice의 어노테이션에다가 사용할 어노테이션 정의하기

 @Component
 @Aspect
public class PerfAspect {
    @Around(" @annotation(PerfLoggin)") //<-요부분
    public Object logPerf(ProceedingJoinPoint pip) throws Throwable {
        Long begin = System.currentTimeMillis();
        Object retVal = pip.proceed();
        System.out.println(System.currentTimeMillis() - begin);
        return retVal;
    }
}

2. 패키지 루트로 정의하기

 @Component
 @Aspect
public class PerfAspect {
    @Around("execution(* com.example.*.EventService.*(..))")
    public Object logPerf(ProceedingJoinPoint pip) throws Throwable {
        Long begin = System.currentTimeMillis();
        Object retVal = pip.proceed();
        System.out.println(System.currentTimeMillis() - begin);
        return retVal;
    }
 }
  • 패키지의 경로를 루트부터 입력하고 *표는 모든 패키지, 클래스 또는 메서드를 의미함

3. bean id로 빈에 정의하기

@Component
@Aspect
public class PerfAspect {
   @Around("bean(simpleEventService)") //해당 빈의 모든 클레스에 적용 (bean id)
   public Object logPerf(ProceedingJoinPoint pip) throws Throwable {
       Long begin = System.currentTimeMillis();
       Object retVal = pip.proceed();
       System.out.println(System.currentTimeMillis() - begin);
       return retVal;
   }
}
profile
3개국어 쌉가능한 주니어 개발자

0개의 댓글