Posts [개발자 블로그] Spring AOP
Post
Cancel

[개발자 블로그] Spring AOP

AOP란?

  • Aspect Oriented Programming의 약자로 관점 지향 프로그래밍을 뜻함
  • 핵심 비지니스 로직과 공통 모듈(횡단 관심사)을 분리함으로써 개발자가 좀 더 비지니스 로직에만 집중해서 처리할 수 있는 방법을 제공하는 프로그래밍 패러다임

2

  • 핵심기능 관점은 위의 그림과 같이 각 Service모듈이 각각 자신만의 역할을 수행하기에 공통된 모듈을 발견할 수가 없을 것이다.

스크린샷 2021-05-03 오후 5 16 14

  • 위의 그림처럼 부가기능의 관점에서 바라보면 각각의 Service는 before()메서드와 after()메서드가 공통됨을 알 수 있을 것이다.
  • AOP는 기존에 OOP에서 바라보던 관점을 다르게 하여 부가기능적인 측면에서 보았을때 공통된 요소를 추출하자는 것이다.
  • 가로(횡단) 영역의 공통된 부분을 잘라냈다고 하여, AOP를 크로스 컷팅(Cross-Cutting)이라고도 부른다.
  • 부가기능 모듈을 Aspect라 부르며 애플리케이션 전반적으로 흩어진 부가기능(Aspect)를 모듈화하여 재사용할 수 있도록 지원하는 프로그래밍 기법

AOP와 OOP의 차이

  • OOP와 AOP는 둘 다 공통된 기능을 재사용하는 기법이며 AOP는 OOP를 보완한다.
  • 프로그램 구조에 대해서 다른 생각의 방향을 제시하면서 AOP가 OOP를 보완하고 있단 뜻
  • OOP는 객체를 재사용함으로써 반복되는 코드의 양을 굉장히 많이 줄일수가 있다.
  • 각 객체들이 가지고 있는 로그, 권한 체크, 인증, 예외 처리 등 은 필수적이기에 중복되는 부분이 발생할 수 밖에 없다.
  • OOP에서 공통된 기능을 재사용하는 방법으로 상속이나 위임을 사용하지만 전체 애플리케이션에서 사용되는 부가기능들을 상속이나 위임으로 처리하기에는 깔끔하게 모듈화하기가 어려움.
  • 즉, AOP는 객체의 공통된 부가 기능(로그, 권한 체크, 인증, 예외 처리 등)을 분리하여 모듈화함으로써 프로젝트 전체의 유지 보수, 재사용을 용이하게 도와준다.

  • OOP : 비즈니스 로직의 모듈화(모듈화의 핵심 단위는 비즈니스 로직)
  • AOP : 인프라 혹은 부가기능의 모듈화(각각의 모듈들의 주 목적 외에 필요한 부가적인 기능들)
    • 대표적 예: 로깅, 트랜잭션, 보안 등

AOP의 두 가지 장점

  • 1)어플리케이션 전체에 흩어진 공통 기능이 하나의 장소에서 관리됨
  • 2)다른 서비스 모듈들이 본인의 역할에만 충실하고 그외 부가적인 기능들은 신경쓰지 않아도 됨

AOP의 구현체

스크린샷 2021-05-03 오후 7 36 55

  • 다양한 언어로 구현이 되어있는데 Java는 AspectJ를 사용함

AOP 용어

Target

  • 부가기능을 부여할 대상
  • 예를 들어, Service Layer(getBoards혹은 getUsers를 하는 Service들)

Aspect

  • 실제 부가기능 모듈
  • 객체지향 모듈을 오브젝트라 부르는 것과 비슷하게 부가기능 모듈을 Aspect라 부름
  • 핵심 기능에 부가되어 의미를 갖는 특별한 모듈
  • Aspect는 부가될 기능을 정의한 Advice와 Advice를 어디에 적용할지를 결정하는 Point cut으로 구성됨

Advice

  • 실제 적용될 부가 기능
  • 실질적으로 부가기능을 담은 구현체
  • 타겟 오브젝트에 종속되지 않기 때문에 순수하게 부가기능에만 집중할 수 있음
  • ‘무엇’을 ‘언제’를 정의(무엇 -> 해당 어노테이션이 달린 메소드, 언제 -> 어노테이션의 종류)
  • Advice의 종류
    • 1)Before : 메서드 호출 이전
    • 2)After : 메서드 호출 이후
    • 3)AfterReturning : 메서드가 예외없이 실행된 이후
    • 4)AfterThrowing : 메서드에 예외가 발생한 이후
    • 5)Around : 메서드의 실행 전/후

Point cut

  • 여러 Join point중에서 Advice가 적용될 대상(Spring에선 메소드)을 선정하는 방법
  • 포인트컷은 포인트컷 표현식으로 명시되는데 이 표현식은 포인트컷 지정자와 타겟명세로 구성됨

1)포인트 컷 지정자

  • 9가지의 지정자가 있지만 실제로 execution과 @annotation을 주로 사용
  • 1)excution()
    • 접근제한자, 리턴타입, 인자타입, 클래스/인터페이스, 메소드명, 파라미터타입, 예외타입 등을 전부 조합가능한 가장 세심한 지정자
    • 풀패키지에 메소드명까지 직접 지정할 수도 있으며, 아래와 같이 특정 타입내의 모든 메소드를 지정할 수도 있다.
    • ex) execution(* com.blogcode.service.AccountService.*(..) : AccountService 인터페이스의 모든 메소드
  • 2)within()
    • excution 지정자에서 클래스/인터페이스까지만 적용된 경우
    • 즉, 클래스 혹은 인터페이스 단위까지만 범위 지정이 가능
    • ex1) within(com.blogcode.service.*): service패키지 아래의 클래스와 인터페이스가 가진 모든 메소드
    • ex2) within(com.blogcode.service..): service 아래의 모든 하위 패키지까지 포함한 클래스와 인터페이스가 가진 메소드
  • 3)args
    • 메소드의 인자가 타겟 명세에 포함된 타입일 경우
    • ex)args(java.io.Serializable): 하나의 파라미터를 갖고, 그 인자가 Serializable타입인 모든 메소드
  • 4)@args
    • 메소드의 인자가 타겟 명세에 포함된 어노테이션 타입을 갖는 경우
    • @args(com.blogcode.session.User): 하나의 파라미터를 갖고, 그 인자의 타입이 @User 어노테이션을 갖는 모든 메소드
      • @User User user같이 인자 선언된 메소드
  • 5)this()
    • 타겟 메소드가 지정된 빈 타입의 인스턴스인 경우
  • 6)target()
    • this와 유사하지만 빈 타입이 아닌 타입의 인스턴스인 경우
  • 7)@target()
    • 타겟 메소드를 실행하는 객체의 클래스가 타겟 명세에 지정된 타입의 어노테이션이 있는 경우
  • 8)@annotation
    • 타겟 메소드에 특정 어노테이션이 지정된 경우
    • ex) @annotation(org.springframework.transaction.annotation.Transactional): Transactional 어노테이션이 지정된 메소드 전부

2)타겟 명세

  • 실제 타겟이 적용될 지점 명시한 것
  • excution(* com.blogcode.board.BoardService.getBoards(..))
  • AND, OR, NOT과 같은 관계연산자를 이용할 수 있음
    1
    2
    
    @Around("execution(* com.blogcode.board.BoardService.getBoards(..)) 
    || execution(* com.blogcode.user.UserService.getUsers(..))")
    
  • 아래와 같이 @Pointcut 어노테이션을 활용하여 특정 표현식을 변수처럼 재사용할 수 있음. 즉, 재사용 가능한 포인트컷을 정의할 수 있음
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    
    @Aspect
    public class Performance {
    
      @Pointcut("execution(* com.blogcode.board.BoardService.getBoards(..))")
      public void getBoards(){}
    
      @Pointcut("execution(* com.blogcode.user.UserService.getUsers(..))")
      public void getUsers(){}
    
    
      // 각각의 표현식을 getBoards() 메소드와 getUsers() 메소드로 미리 선언 후 메소드명으로 표현식을 그대로 사용할 수 있음
      @Around("getBoards() || getUsers()")
      public Object calculatePerformanceTime(ProceedingJoinPoint proceedingJoinPoint) {
          Object result = null;
          try {
              long start = System.currentTimeMillis();
              result = proceedingJoinPoint.proceed();
              long end = System.currentTimeMillis();
    
              System.out.println("수행 시간 : "+ (end - start));
          } catch (Throwable throwable) {
              System.out.println("exception! ");
          }
          return result;
      }
    }
    
  • 타겟 메소드에 전달된 인자를 사용할 수 있음
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    
    @Aspect
    @Component // @Bean과 동일하게 Spring Bean 등록 어노테이션
    public class UserHistory {
    
      @Autowired
      private HistoryRepository historyRepository;
    
      // updateUser라는 포인트컷이 user라는 인자를 사용하도록 args(user) 표현식으로 지정
      @Pointcut("execution(* com.blogcode.user.UserService.update(*)) && args(user)")
      public void updateUser(User user){}
    
      @AfterReturning("updateUser(user)")
      public void saveHistory(User user){
          historyRepository.save(new History(user.getIdx()));
      }
    }
    
  • 표현식의 “args(user)”로 인해 타겟 메소드의 인자와 어드바이스의 인자가 매칭됨
  • 위의 코드는 유저 정보의 변경이 정상적으로 실행될시 변경 기록에도 추가하는 Aspect

Join point

  • 메서드, 필드, 객체, 생성자 등 실제 advice가 적용될 수 있는 지점
  • 다른 AOP 프레임워크와 달리 Spring에서는 메소드 Join point만 제공(그래서 여러 책이나 문서에서 조인포인트에 대해 생략하기도 합니다. 무조건 메소드 단위로만 지정하기 때문입니다.)

Proxy(프록시)

  • Target을 감싸서 Target의 요청을 대신 받아주는 랩핑(Wrapping) 오브젝트
  • Spring AOP는 프록시 패턴이라는 디자인 패턴을 사용해서 AOP효과를 낸다.
    • 프록시 패턴을 사용하면 어떤 기능을 추가하려 할때 기존 코드를 변경하지 않고 기능을 추가할 수 있음
    • 어떤 클래스가 Spring AOP의 대상이라면 그 기존 클래스의 Bean이 생성될때 Spring AOP가 프록시(기능이 추가된 클래스)를 자동으로 만들고 원본 클래스 대신 프록시를 Bean으로 등록한다. 그리고 원본 클래스가 사용되는 지점에 프록시를 대신 사용한다.
  • 예를 들어, OwnerRepository인터페이스의 @Transactional 어노테이션이 사용된다고 가정해보자.
    5
  • @Transactional 어노테이션이 붙어있으면 OwnerRepository 타입의 프록시가 새로 만들어지고 Spring AOP에 의해 자동으로 생성되는 OwerRepository의 프록시에는 @Transactional 어노테이션이 지시하는 코드가 삽입된다.
  • 즉, JDBC에서 트랜잭션 처리를 하려면 SQL 실행문 앞 뒤에 setAutoCommit()와 commit()/rollback() 코드가 항상 붙게 되는데 @Transactional 어노테이션은 프록시에 자동으로 그 코드를 넣어서 반복, 중복되는 코드를 생략할 수 있다. 이로소 개발자는 비즈니스 로직에만 집중할 수 있게 된다.
  • 호출자에서 OwerRepository(타겟)를 호출하게 되면 이를 감싸고 있는 프록시가 호출되어, 메소드에 대한 선처리 및 후처리를 실행시키도록 한다.
  • 즉, AOP에서 프록시는 호출을 가로챈 후, Advice에 등록된 기능을 수행 후 타겟 메소드를 호출한다.
    3

  • Spring AOP Proxy는 CGLIB Proxy, JDK Dynamic Proxy를 사용
    • 과거에는 기본적으로 인터페이스가 있고, 그의 구현체가 있는 클래스의 경우 JDK dynamic Proxy를 사용하고 인터페이스가 없는 경우 CGLIB Proxy를 사용
    • Spring Boot에서는 디폴트로 CGLib Proxy를 생성(성능도 더 좋고 예외도 더 적음)
    • JDK Dynamic Proxy는 Java Reflection을 이용해 조금 속도가 느리다고 함. JDK Dynamic Proxy를 강제로 사용하려면 아래와 같은 설정을 추가하면 됨.
    1
    2
    3
    4
    5
    
      @EnableAspectjAutoProxt(proxyTargetClass = false)
      @Configuration
      public class ProxyConfig {
    
      }
    
    • 예외적으로 spring-data-jpa에서는 JDK Dynamic Proxy를 사용해 Repository를 생성

Introduction(인트로덕션)

  • Target 클래스에 코드 변경없이 신규 메소드나 멤버변수를 추가하는 기능

Weaving(위빙)

  • 공통코드(Advice)를 핵심로직코드에 삽입하는것
  • 지정된 객체에 Aspect를 적용하여 새로운 프록시 객체를 생성하는 과정
  • 예를 들어, A라는 객체에 트랜잭션 Aspect가 지정되어 있다면, A라는 객체가 실행되기전 커넥션을 오픈하고 실행이 끝나면 커넥션을 종료하는 기능이 추가된 프록시 객체가 생성되고, 이 프록시 객체가 앞으로 A객체가 호출되는 시점에 사용된다. 이때의 프록시 객체가 생성되는 과정을 위빙이라 생각하면 된다.
  • 컴파일 타임, 클래스로드 타임, 런타임과 같은 시점에서 실행되지만, Spring AOP는 런타임에서 프록시 객체가 생성 됩니다.

Weaving 방식(시점)

  • 1)Compile-Time Weaving
    • .java파일을 .class파일로 컴파일시 AOP를 적용시키는 방식
    • JAR를 이용하여 Weaving을 하는 경우, Post-Compile Weaving(Binary Weaving)을 사용하며, 일반 소스 코드의 경우, 일반 Compile-Time Weaving을 사용한다.
  • 2)Load-Time Weaving
    • 실제 컴파일된 클래스를 클래스 로더가 JVM에 적재시킬 때 AOP를 적용시키는 방식
    • 다른 Weaving보다 속도 측면에서는 느리다.
  • 3)Run-Time Weaving(프록시 패턴)
    • Spring AOP에서 사용하는 방식
    • 특정 기능을 프록시로 감싸서 수행하는 패턴
    • 어떤 클래스가 Spring AOP의 대상이라면 그 기존 클래스의 Bean이 생성될때 Spring AOP가 프록시(기능이 추가된 클래스)를 자동으로 만들고 원본 클래스 대신 프록시를 Bean으로 등록한다. 그리고 원본 클래스가 사용되는 지점에 프록시를 대신 사용한다.
    • 즉, 프록시 Bean 생성하는 것, WAS 가동 후 IoC 컨테이너 초기화 작업할 때

실습(실습 코드)

1. Aspect를 선언

  • Performance.java
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    
    @Aspect
    public class Performance {
    
      @Around("execution(* com.blogcode.board.BoardService.getBoards(..))")
      public Object calculatePerformanceTime(ProceedingJoinPoint proceedingJoinPoint) {
          Object result = null;
          try {
              long start = System.currentTimeMillis();
              result = proceedingJoinPoint.proceed();
              long end = System.currentTimeMillis();
    
              System.out.println("수행 시간 : "+ (end - start));
          } catch (Throwable throwable) {
              System.out.println("exception! ");
          }
          return result;
      }
    }
    

2. Spring 컨테이너의 Bean으로 등록

  • Application.java
    1
    2
    3
    4
    
    @Bean
    public Performance performance() {
      return new Performance();
    }
    

3. Bean으로 등록된 Aspect의 프록시를 생성하고, 어노테이션을 해석할 수 있도록 활성화

  • Application.java
    1
    2
    3
    4
    5
    6
    
    @SpringBootApplication
    @RestController
    @EnableAspectJAutoProxy //오토 프록싱
    public class Application implements CommandLineRunner{
      ...
    }
    

설명

4

  • Advice(@Around)
    • Advice는 ‘무엇을’, ‘언제’ 할지를 의미
    • 무엇: calculatePerformanceTime() 메소드
    • 언제: @Around로 타겟 메소드 호출 전과 후에
    • 예를 들어, 타겟 메소드의 호출 전에만 적용하려면
      1
      2
      3
      4
      
        @Before("포인트컷 표현식")
        public void 어드바이스메소드() {
        ....
        }
      
    • 주의할 점은 @Around의 경우 반드시 proceed() 메소드가 호출되어야 한다는 것
    • proceed() 메소드는 타겟 메소드를 지칭하기에 proceed메소드를 실행시켜야만 타겟 메소드가 수행된다는 것에 주의
  • Point cut(“execution(* com.blogcode.board.BoardService.getBoards(..))”)
    • Advice의 value로 들어간 문자열을 포인트컷 표현식이라고 함
    • 포인트컷 표현식은 2가지로 나눠짐
      • 1)포인트컷 지정자: execution
      • 2)타겟 명세: (* com.blogcode.board.BoardService.getBoards(..))
  • 자기가 자신의 메서드를 실행할 때는 AOP가 적용되지 않음!

AOP의 활용 방법

  • 1) 트랜잭션
  • 2) 로깅
  • 3) 인증, 인가(권한 체크)
  • 4) 캐시 추상화
  • 5) 메소드의 실행시간 측정
  • 6) Dao에서 Service를, Service에서 Controller를 호출하지 못하도록 막을때(계층형 구조에서 하위계층이 상위 계층을 호출하는 것은 금지하므로)

Spring AOP vs AspectJ

 Spring AOPAspect J
목표간단한 AOP 기능 제공완벽한 AOP기능 제공
join point메서드 레벨만 지원생성자, 필드, 메서드 등 다양하게 지원
weaving런타임 시에만 가능런타임은 제공하지 않음. compile-time, post-compile, load-time 제공
대상Spring Container가 관리하는 Bean에만 가능모든 Java Object에 가능

출처

This post is licensed under CC BY 4.0 by the author.