[스프링 코어] 5. 스프링 AOP

최진민·2021년 2월 1일
0

스프링 코어

목록 보기
6/8
post-thumbnail

개념 소개

  • AOP(Aspect-oriented Programming)은 OOP(Object-oriented Programming)를 보완하는 수단, 흩어진 Aspect를 모듈화할 수 있는 기법.

  • 흩어진 관심사 (Crosscutting Concerns)

    • Concerns(관심사) : 여러 클래스와 메소드들 중 비슷한 코드

    • 3개의 클래스와 색으로 배치된 Concerns

    • 🎈AOP를 적용

      • Aspect(색 띠)에 해당하는 기능을 어디(A, B, C)에 적용해야 되나
  • ✨AOP 주요 개념✨ (AOP 관련된 용어가 어려워 포기하는 사람이 많다... 그렇게 생각하지말자)

    • Aspect & Target
      • Aspect : 묶은 것 = 모듈(하얀 박스)
      • 모듈에 들어가는 것 ⇒ Advice와 PointCut
      • Target : 적용이 되는 대상(Class A, B, C)(녹색 박스)
    • Advice : 해야할 일들(색 띠)
    • Join point & PointCut
      • Join point : 합류점, 메소드 실행 시점(등 그 외 여러 지점)에 Advice를 끼워 넣어라.
      • Pointcut : Join pointSubSet, 어디에 적용 해야하는지 (회색 박스 안 A,B,C)
  • AOP 구현체

  • 🧨AOP 적용 방법

    • (1) 컴파일 타임
      • A.java —> AOP —> A.class (제공 : AspectJ)
      • : 자바코드에는 없지만 컴파일을 한 코드에는 있는 것처럼 컴파일 해주는!
      • 단점 : 별도의 컴파일 과정을 거쳐야한다.
      • 장점 : 코드 타임과 런타임에서 올 수 있는 부하가 없다.
    • (2) 로드 타임
      • A.java → A.class —> AOP(위치 : 클래스 로더) —> 메모리 (제공 : AspectJ)
      • 클래스를 로딩하는 시점.
      • 단점 : 약간의 성능 부하와 설정이 필요하다.
      • 장점 : 다양한 문법 사용가능(AspectJ)
    • (3) 런타임
      • A라는 클래스 타입의 빈을 만들때, A 타입을 감싸는 Proxy 빈을 만들어서 Porxy 빈이 갖고 있는 메소드를 실행시키기 전에 내용을 먼저 보여준다.
      • 스프링 AOP, 디자인 패턴(=Proxy pattern)을 사용해서 AOP와 같은 효과를 내는 방법.
      • 단점 : 초기에 약간의 빈을 설정해야한다는 점.
      • 장점 : 실제 App.에서 Request가 들어올 때, 성능은 문제 없다. 별도의 설정 필요 없고 문법이 필요 없다.

프록시 기반 AOP

  • 스프링 AOP 특징

    • 🎈프록시 기반의 AOP 구현체
    • 🎈스프링 빈에만 AOP를 적용할 수 있다.
    • 모든 AOP 기능 제공 x, 스프링 IoC와 연동하여 Enterprise App.에서 발견되는 흔한 문제에 대한 해결책 제공
  • What is 프록시 패턴❓

    • Why? (기존 코드 변경 없이) 접근 제어 또는 부가 기능 추가

      • Client는 인터페이스 타입으로 Porxy 객체를 사용하고 프록시 객체는 원 타겟 객체를 참조하고 있다.

      • 예제를 통한 프록시 패턴 확인

        • main\java\me\jinmin\AppRunner : Client

          package me.jinmin;
          
          import org.springframework.beans.factory.annotation.Autowired;
          import org.springframework.boot.ApplicationArguments;
          import org.springframework.boot.ApplicationRunner;
          import org.springframework.stereotype.Component;
          
          @Component
          public class AppRunner implements ApplicationRunner {
          
              @Autowired
              EventService eventService;
          
              @Override
              public void run(ApplicationArguments args) throws Exception {
                  eventService.createEvent();
                  eventService.publishEvent();
                  eventService.deleteEvent();
              }
          }
        • main\java\me\jinmin\EventService : interface Subject

          package me.jinmin;
          
          public interface EventService {
              void createEvent();
          
              void publishEvent();
          
              void deleteEvent();
          }
        • main\java\me\jinmin\SimpleEventService : Real Subject(=구현체)

          package me.jinmin;
          
          import org.springframework.stereotype.Service;
          
          @Service
          public class SimpleEventService implements EventService{
          
              @Override
              public void createEvent() {
          
                  //long begin = System.currentTimeMillis();
          
                  try {
                      Thread.sleep(1000);
                  } catch (InterruptedException e) {
                      e.printStackTrace();
                  }
                  System.out.println("Created an event!");
          
                  //System.out.println(System.currentTimeMillis() - begin);
              }
          
              @Override
              public void publishEvent() {
          
                  //long begin = System.currentTimeMillis();
          
                  try {
                      Thread.sleep(2000);
                  } catch (InterruptedException e) {
                      e.printStackTrace();
                  }
                  System.out.println("Published an event!");
          
                  //System.out.println(System.currentTimeMillis() - begin);
          
              }
          
              @Override
              public void deleteEvent(){
                  System.out.println("Delete an event!");
              }
          }
        • 결과

          //주석 포함 실행
          print:
          Created an event!
          1002
          Published an event!
          2001
          Delete an event!
        • ❗기존 코드를 변경하지 않고 성능을 측정하자.(❤프록시 패턴으로) ⇒ main\java\me\jinmin\ProxySimpleEventService

          package me.jinmin;
          
          import org.springframework.beans.factory.annotation.Autowired;
          import org.springframework.context.annotation.Primary;
          import org.springframework.stereotype.Service;
          
          @Primary
          @Service
          public class ProxySimpleEventService 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();
              }
          }
          //주석들을 지우고
          print:
          Created an event!
          1005
          Published an event!
          2006
          Delete an event!
    • 프록시 패턴의 문제점

      • 프록시 내에서도 중복 코드가 생성된다는 점
      • 프록시를 생성하는데 드는 비용(모든 클래스를 생성, 메소드마다 deligation(위임)이 필요)
      • 여러 클래스, 여러 메소드에 적용하려면 많은 비용이 든다.
      • 프록시 패턴의 단점을 보완
        • 프록시 패턴의 클래스를 생성하는 것이 아니라 동적 프록시 객체 생성(Runtime). ⇒ ❤ 스프링 AOP
  • 스프링 AOP

    • 동적 프록시
      • 자바 : 인터페이스 기반 프록시 생성
      • CGlib는 클래스 기반 프록시도 지원
    • 스프링 IoC : 기존 빈을 대체하는(=감싸는) 동적 프록시 빈을 만들어 등록

@AOP

  • 의존성 추가

    <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-aop</artifactId>
            </dependency>
  • main\java\me\jinmin\ProxySimpleEventService 삭제.

  • 두 가지 정보의 필요성

    • 해야할 일(Advice)
    • 해야할 일을 어디에 적용할 것인가(PointCut)
  • main\java\me\jinmin\PerfAspect

    • (이 외 모든 클래스는 위의 내용과 동일)

      package me.jinmin;
      
      import org.aspectj.lang.ProceedingJoinPoint;
      import org.aspectj.lang.annotation.Around;
      import org.aspectj.lang.annotation.Aspect;
      import org.springframework.stereotype.Component;
      
      @Component
      @Aspect
      public class PerfAspect {
      
          @Around("execution(* me.jinmin.EventService.*(..))") //PointCut을 새로 정의할 수 있고 어디에 적용할지 정의할 수 있다.
          public Object logPerf(ProceedingJoinPoint pip) throws Throwable { // pip : Advice가 적용되는 대상(여기서는 SimpleEventService의 두 메소드)
              long begin = System.currentTimeMillis();
              Object retVal = pip.proceed();
              System.out.println(System.currentTimeMillis() - begin);
              return retVal;
          }
      }
    • 메소드 호출 전, 후 무언가를 할 수 있다. (에러를 발견해 처리할 수도)@Around = PointCut 정의(현재는 execution을 사용, 밑에 문제점에서는 @annotation을 사용)

    • 결과

      print:
      Created an event!
      1077
      Published an event!
      2008
      Delete an event!
      1
  • 문제점 : deleteEvent()에는 적용하고 싶지 않다. → Annotation(@Retension)을 통해 해결❗(주의 : @Retension을 클래스 레벨로 설정)

  • \main\java\me\jinmin\PerfLogging

    package me.jinmin;
    
    import java.lang.annotation.*;
    
    /**
     * If, use this annnotation, log the performance.
     */
    
    @Documented
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.CLASS)
    public @interface PerLogging {
    
    }
    • @Retension 에서 RetensionPoicy : 이 애노테이션 정보를 얼마나 유지할 것인가.
      • @Retention(RetentionPolicy.SOURCE : 컴파일하면 정보가 사라진다.
      • @Retention(RetentionPolicy.RUNTIME : 굳이 설정할 필요 없다.
      • @Retention(RetentionPolicy.CLASS : Default (웬만하면)
  • \main\java\me\jinmin\PerfAspect

    package me.jinmin;
    
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.Around;
    import org.aspectj.lang.annotation.Aspect;
    import org.springframework.stereotype.Component;
    
    @Component
    @Aspect
    public class PerfAspect {
    
        @Around("@annotation(PerLogging)") //애노테이션 처리
        public Object logPerf(ProceedingJoinPoint pip) throws Throwable { // pip : Advice가 적용되는 대상(여기서는 SimpleEventService의 두 메소드)
            long begin = System.currentTimeMillis();
            Object retVal = pip.proceed();
            System.out.println(System.currentTimeMillis() - begin);
            return retVal;
        }
    }
  • \main\java\me\jinmin\SimpleEventService

    package me.jinmin;
    
    import org.springframework.stereotype.Service;
    
    @Service
    public class SimpleEventService implements EventService{
    
        @PerLogging
        @Override
        public void createEvent() {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("Created an event!");
        }
    
        @PerLogging
        @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("Delete an event!");
        }
    }
    • 결과

      print:
      Created an event!
      1056
      Published an event!
      2007
      Delete an event!
  • 🎈포인트컷 정의

    • @PointCut(표현식)@Around
    • 주요 표현식
      • execution
      • @annotation
      • bean
        • bean 예)
        • @Around("bean(simpleEventService") 는 빈에 해당하는 모든 메소들에 적용
    • 포인트컷 조합
      • &&, ||, ! (execution에서는 사용 불가)
  • 🎈어드바이스 정의

    • @Before : 구현체의 메소드 실행 이전에 @Before로 정의한 메소드들이 먼저 실행
    • @AfterReturning
    • @AfterThrowing
    • @Arond : 강력.
    • 그 외
profile
열심히 해보자9999

0개의 댓글