[Spring] 스프링 삼각형: AOP

공부하는 감자·2024년 1월 30일
0

Spring Framework

목록 보기
3/5
post-thumbnail

AOP

AOP란?

AOP는 Aspect-Oriented Programming의 약자이다. 이는 관점(Aspect) 지향 프로그래밍이라고 부른다.

스프링 DI가 의존성에 대한 주입이라면, 스프링 AOP는 로직(code) 주입이라고 할 수 있다.

횡단 관심사

  • 다수의 모듈에 공통적으로 나타나는 부분을 횡단 관심사(Cross-Cutting Concerns)라고 한다.
  • 모듈 별로 핵심이 되는 코드는 핵심 관심사라고 한다.

예를 들어, 다음 그림에서는 입금, 출금, 이체 모듈에서 로깅, 보안, 트랜잭션 기능이 반복적으로 나타나고 있다.

  • 횡단 관심사: 로깅, 보안, 트랜잭션
  • 핵심 관심사: 입금, 출금, 이체

예: DB 커넥션

코드=핵심 관심사+횡단 관심사코드 = 핵심\ 관심사 + 횡단\ 관심사

프로그램에서 데이터베이스를 연동할 경우, 쿼리를 날릴 때 항상 DB 커넥션을 맺어줘야 한다.

  • 5번은 핵심 관심사이다.
  • 5번을 제외한 나머지 작업은 횡단 관심사이다.
    • 어떤 데이터베이스 연산을 하든 공통적으로 나타나는 코드이다.
1) DB 커넥션 준비
2) Statement 객체 준비

try {
	3) DB 커넥션 연결
	4) Statement 객체 세팅
	5) 쿼리 실행
} catch ... {
	6) 예외 처리
} finaly {
	7) DB 자원 반납
}

로직 주입

스프링 AOP는 로직 주입이라고 했는데, 객체 지향에서 로직(코드)가 있는 곳은 당연히 메서드의 안쪽이라고 할 수 있다.

메서드의 코드 주입 위치

메서드에서 코드를 주입할 수 있는 곳은 총 5군데가 있다.

  • Around : 메서드 전 구역
  • Before : 메서드 시작 직후
  • After : 메서드 종류 직전
  • AfterReturning : 메서드 정상 종료 후
  • AfterThrowing : 메서드에서 예외가 발생하면서 종료된 후

그림 출처: Love Me Some Coding | Love Some Coding | Love Coding | Folau Kaveinga

예: 스프링 AOP 적용 예제

다음은 기존 코드에 AOP를 적용하는 예제이다.

기존 코드

  • Boy 클래스와 Girl 클래스
    • do something 부분을 제외한 나머지 부분은 모두 공통적으로 나타난다.
public class Boy {
    public void runSomething() {
        System.out.println("Start");

        try {
            // do something
        } catch (Exception e) {
            System.out.println(e.getMessage());
        } finally {
            System.out.println("Exit");
        }

        System.out.println("End");
    }
}

public class Girl {
    public void runSomething() {
        System.out.println("Start");

        try {
            // do something
        } catch (Exception e) {
            System.out.println(e.getMessage());
        } finally {
            System.out.println("Exit");
        }

        System.out.println("End");
    }
}
  • main() 메서드
public class Start {
    public static void main(String[] args) {
        Boy romeo = new Boy();
        Girl juliet = new Girl();

        romeo.runSomething();
        juliet.runSomething();
    }
}

인터페이스 기반 AOP

먼저, Boy와 Girl의 공통 메서드를 추려 인터페이스를 생성한다.

  • 인터페이스 생성
public interface Person {
	void runSomething();
}

이후 인터페이스를 구현하면서 횡단 관심사를 제거한다.

  • 기존 클래스 수정
    • 횡단 관심사를 모두 제거하고, 핵심 관심사만 남았다.
    • 단일 책임 원칙 (SRP)가 자연스럽게 적용됨
public class Boy implements Person {
    public void runSomething() {
        // do something
    }
}

public class Girl implements Person {
    public void runSomething() {
        // do something
    }
}

기존 코드에서 제거한 횡단 관심사는 따로 클래스로 만들어 준다.

  • 횡단 관심사 클래스 추가
    • @Aspect : 이 클래스를 AOP에서 사용하겠다는 의미
    • @Before : 대상 메서드 실행 전에 이 메서드를 실행하겠다는 의미
    • JoinPoint : @Before 에서 선언된 Boy.runSomething() 메서드를 의미
  • Boy 클래스의 runSomething() 메서드가 시작된 직후에 코드를 삽입한다.
  • Girl 클래스에도 횡단 관심사를 적용하기 위해선 코드를 수정해야 한다.
    • @Before("execution(* runSomething())") 로 수정
@Aspect
public class MyAspect {
    @Before("execution(public void Boy.runSomething())")
    public void before(JoinPoint joinPoint) {
        System.out.println("Start");
    }
}

실제 코드를 실행하면, 분리했던 횡단 관심사가 아래처럼 들어가게 된다.

public class Boy implements Person {
    public void runSomething() {
		**System.out.println("Start");**
        // do something
    }
}

이후, 스프링 프레임워크 기반에서 구동되도록 main() 메서드를 수정하고 설정 파일을 추가한다.

  • main() 메서드 수정
    • context.xml 설정 파일 적용
    • boy 빈 객체를 가져와 runSomething() 실행
public class Start {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("/context.xml");

        Person romeo = context.getBean("boy", Person.class);

        romeo.runSomething();
    }
  • 스프링 설정 파일 (context.xml)은 다음과 같다.
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="
	       http://www.springframework.org/schema/aop 
	       http://www.springframework.org/schema/aop/spring-aop-3.1.xsd
	       http://www.springframework.org/schema/beans
	       http://www.springframework.org/schema/beans/spring-beans.xsd">

    <aop:aspectj-autoproxy />
    
    <bean id="myAspect" class="MyAspect" />
    <bean id="boy" class="Boy" />
</beans>

CGLiB 라이브러리

CGLiB 라이브러리를 사용하면 인터페이스 없이 AOP를 적용할 수 있다.

  • 코드를 변경할 수 없는 서드 파티 모듈 안에 final로 선언된 클래스에 AOP를 적용해야 하는 경우 사용한다.
  • 참고 서적에선 CGLiB 라이브러리 사용은 추천하지 않는다.

AOP와 프록시 패턴

스프링은 프록시 패턴을 이용해 횡단 관심사를 핵심 관심사에 주입한다.

  • 호출하는 쪽에서 romeo.runSomething() 을 호출하면, 프록시가 그 요청을 받아 진짜 romeo 객체(인스턴스)에게 요청을 전달한다.
  • 중간의 runSomething() 메서드에서 주고받는 내용을 감시하거나 조작할 수 있다.
    • 그림의 파란색 메서드
  • 스프링 프레임워크만 프록시의 존재를 안다.
    • 호출하는 쪽과 호출당하는 쪽 모두 프록시의 존재를 모른다.

따라서, 예제 코드를 보면 설정 파일에서 다음과 같은 코드를 사용했었다.

  • 자바(j) 횡단 관심사(aspect)에 자동으로 프록시 삽입
  • 즉, 스프링 프레임워크에게 AOP 프록시를 사용하라고 알려주는 지시자이다.
<aop:aspectj-autoproxy />

스프링 AOP의 핵심

  • 스프링 AOP는 인터페이스(interface) 기반이다.
  • 스프링 AOP는 프록시(Proxy) 기반이다.
  • 스프링 AOP는 런타임(runtime) 기반이다.

용어

용어의미
AspectAdvisor의 집합체
Advisor어디서, 언제, 무엇을
AdvicePointcut에 적용할 로직
JoinPointAspect 적용이 가능한 모든 지점
PointcutAspect 적용 위치 지정자

Aspect

  • AOP에서 Aspect는 여러 개의 Advice와 여러 개의 Pointcut의 결합체를 의미하는 용어다.
Aspect=Advice+PointcutAspect = Advice들+Pointcut들
  • Advice는 ‘언제, 무엇을’을 의미하고 Pointcut은 ‘어디에’를 의미한다. 즉, Aspect는 ‘언제, 어디에, 무엇을’이라는 의미이다.

예제에선 다음과 같이 사용한다.

  • Pointcut인 public void Boy.runSomething()
  • @Before : 시작되기 전에
  • public void before() : before() 메서드를 실행하라
@Aspect
public class MyAspect {
    @Before("execution(public void Boy.runSomething())")
    public void before(JoinPoint joinPoint) {
        System.out.println("Start");
    }
}

Advisor

Advisor=한 개의 Advice+한 개의 PointcutAdvisor = 한\ 개의\ Advice+한\ 개의\ Pointcut
  • 스프링 AOP에서만 사용하는 용어다.
  • 스프링 버전이 올라가면서 이제는 사용하지 말라고 권고하는 기능이다.
    • 다수의 Advice와 다수의 Pointcut을 다양하게 조합해서 사용할 수 있는 방법(Aspect)가 나왔기 때문이다.

Advice

  • pointcut에 언제, 무엇을 적용할지 정의한 메서드다.
  • 타깃 객체의 타깃 메서드에 적용될 부가 기능이라고 표현하기도 한다.

예제에선 다음과 같이 사용한다.

  • @Before : 지정된 Pointcut이 시작되기 전
  • public void before() : before() 메서드를 실행
@Aspect
public class MyAspect {
    **@Before**("execution(public void Boy.runSomething())")
    public void before(JoinPoint joinPoint) {
        System.out.println("Start");
    }
}

JoinPoint

  • 스프링 AOP는 메서드에만 적용 가능하다.
    • 스프링 AOP는 인터페이스 기반이고, 인터페이스는 추상 메서드의 집합체이기 때문이다.
  • Pointcut의 후보가 되는 모든 메서드들을 Aspect 적용이 가능한 지점, 즉 JoinPoint이다.
  • Pointcut은 JoinPoint의 부분 집합이다.
  • JoinPoint 파라미터를 이용하여 메서드의 정보를 확인할 수 있다.
    • 실행 시점에 실제 호출된 메서드가 무엇인지
    • 실제 호출된 메서드를 소유한 객체가 무엇인지
    • 호출된 메서드의 파라미터는 무엇인지

위의 예제에 따르면, JoinPoint는 다음(볼드체)과 같다.

  • romeo.runSomething() 을 호출한 상태일 경우, JoinPoint는 romeo 객체의 runSomething() 메서드이다.
  • juliet.runSomething() 을 호출한 상태일 경우, JoinPoint는 juliet 객체의 runSomething() 메서드이다.
@Aspect
public class MyAspect {
    @Before("execution(public void Boy.runSomething())")
    public void before(**JoinPoint joinPoint**) {
        System.out.println("Start");
    }
}

Pointcut

  • 횡단 관심사를 적용할 타깃 메서드를 선택하는 지시자(메서드 선택 필터)이다.
  • 스프링 프레임워크가 아닌, 다른 AOP 프레임워크에서는 메서드 뿐만 아니라 속성 등에도 Aspect를 적용할 수 있다.
  • 타깃 메서드 지정자에는 정규식과 AspectJ 표현식 등을 사용할 수 있다.
// 대괄호는 생략 가능하다는 의미이다.
[접근제한자패턴] **리턴타입패턴** [패키지&클래스패턴.]**메서드이름패턴(파라미터패턴)** [throws 예외패턴]

// 사용 예제
@Before("execution( **public void Test.Boy.runSomething()** )")
@Before("execution( *** runSomething()** )")

Reference

참고 서적

📔 스프링 입문을 위한 자바 객체 지향의 원리와 이해

인프런 강의

스프링 핵심 원리 - 기본편 강의 - 인프런

profile
책을 읽거나 강의를 들으며 공부한 내용을 정리합니다. 가끔 개발하는데 있었던 이슈도 올립니다.

0개의 댓글