[Spring] AOP(관점 지향 프로그래밍)

GilLog·2020년 11월 1일
7

Spring

목록 보기
3/22

AOP(Aspect Oriented Programming)

AOP는 스프링의 가장 중요한 세 가지 특징인 IoC(제어의 역전), DI(의존성 삽입), AOP(관점 지향 프로그래밍)중 하나이다.

AOPOOP를 대신하는 새로운 개념이 아니라, 기존 OOP를 더욱 보완, 확장하여 OOPOOP답게 사용할 수 있도록 도와주는 개념이다.

자바에서 AOP도구 중 대표적으로는 AspectJ, JBossAOP, SpringAOP가 존재 한다.

OOP를 통해서 지금까지 객체를 재사용함으로써 개발자들은 반복되는 코드의 양을 굉장히 많이 줄일수가 있었다.

하지만 AOP 도입 전까지는 객체의 재사용에도 불구하고 필수적으로 반복되는 코드를 없앨수는 없었다.
트랜잭션, 로그, 권한 체크, 인증, 예외 처리 등

AOP의 필요성을 이해하는 가장 기초가 되는 개념은 '관심의 분리(Separation of Concerns)' 이다.

기능을 비지니스 로직과 공통 모듈로 구분한 후에 개발자의 코드 밖에서 필요한 시점에 비지니스 로직에 삽입하여 실행되도록 한다.
핵심관점(비즈니스 로직) + 횡단관점(트랜잭션, 로그, 권한 체크, 인증, 예외 처리 등)으로 관심의 분리를 실현

즉, OOP에서는 공통적인 핵심 기능을 각 객체의 종단으로 입력했다면, AOP는 핵심 기능에서 중복되는 공통적인 기능을 종단간으로 삽입할 수 있도록 한 것이다.
DI가 의존성(new)의 주입이라면, AOP는 기능(logic)의 주입이라 할 수 있다.

AOP를 통해서 중복 코드 제거, 효율적인 유지보수, 높은 생산성, 재활용성 극대화, 변화 수용 용이 등의 이점을 얻을 수 있다.

위 그림에서 처럼 계좌이체, 입출금, 이자계산이라는 로직을 처리할 때, 모두 똑같이 로깅, 보안, 트랜잭션을 처리해줘야한다.

따라서 모든 로직에 똑같은 코드가 반복적으로 삽입될 수 밖에 없다.

하지만 AOP에서는 로깅, 보안, 트랜잭션이라는 공통 관심(Aspect)을 따로 빼내어 계좌이체, 입출금, 이자계산이라는 핵심 관심에 횡단으로 삽입해 주는 것이다.

이렇게 각 객체별로 처리했던 것을 각 관점별로 외부에서 접근을 하는것이 AOP의 핵심이다.

즉 개발자는 계좌이체, 입출금, 이자계산과 같은 핵심 기능을 만들고, 공통적인 관심 기능(로깅, 보안, 트랜잭션)을 처리하는 모듈을 분리해서 개발한 후, 필요한 시점에 자동으로 공통적인 관심 기능이 삽입되도록 하는것이다.


AOP 용어

  • Joinpoint
    Advice를 적용 가능한 지점을 의미하며, 메소드를 호출하는 '시점', 예외가 발생하는 '시점'과 같이 애플리케이션을 실행할 때 특정 작업이 실행되는 '시점'을 의미한다.
    스프링은 프록시를 이용해서 AOP를 구현하기 때문에 필드 값 변경에 대한 Joinpoint는 불가
    메소드 호출에 대한 Joinpoint 가능
  • Aspect
    Advice와 Pointcut을 합쳐서 하나의 Aspect라고 한다.
    여러 객체에 공통으로 적용되는 기능으로, 즉 일정한 패턴을 가지는 클래스에 Advice를 적용하도록 지원할 수 있는 것을 Aspect라고 한다.
    트랜잭션 기능/로그 기능/보안 기능/인증 기능 등

  • Weaving
    AOP에서 Joinpoint들을 Advice로 감싸는 과정을 Weaving이라고 한다.
    Weaving 하는 작업을 도와주는 것이 AOP 툴이 하는 역할이다.

  • Advice
    Joinpoint에서 실행되어야 하는 코드, 공통 관심, 횡단 관점에 해당한다.
    언제, 어떤 공통 관심 기능을 핵심 로직에 적용할 지를 정의 하고 있다.
    [EX] 메소드 호출 전(언제)에 트랜잭션 시작(공통 기능)기능을 적용한다는 것을 정의

  • Target
    실질적인 비지니스 로직을 구현하고 있는 코드, 핵심 관점에 해당한다.
    Advice를 받을 대상, 즉 객체로 비지니스 로직을 수행하는 클래스일수도 있지만, 프록시 객체(Object)가 될 수도 있다.

  • Pointcut
    Joinpoint의 부분 집합으로서 실제 Advice가 적용되는 Joinpoint를 나타낸다.
    스프링에서는 정규식이나 AspectJ 문법을 이용해, Target 클래스와 Advice가 결합(Weaving)될 때 둘 사이의 결합 규칙을 정의할 수 있다.
    [EX] Advice가 실행된 Target의 특정 메소드등을 지정


Pointcut 표현식

Pointcut을 이용하면 Advice 메소드가 적용될 비즈니스 메소드를 정확하게 필터링 할 수 있다.

  • 지시자(PCD, AspectJ pointcut designators)의 종류

execution : 가장 정교한 Pointcut을 만들수 있고, 리턴타입 패키지경로 클래스명 메소드명(매개변수)
within : 타입패턴 내에 해당하는 모든 것들을 Pointcut으로 설정
bean : bean이름으로 Pointcut

  • return 타입 지정
표현식설명
*모든 리턴타입 허용
void리턴타입이 void인 메소드 선택
!void리턴타입이 void가 아닌 메소드 선택
  • 패키지 지정
표현식설명
com.gil.demo.controllercom.gil.demo.controller 패키지만 선택
com.gil.demo.controller..com.gil.demo.controller 패키지로 시작하는 모든 패키지 선택
  • 클래스 지정
표현식설명
MemberDTO정확하게 MemberDTO 클래스만 선택
*DTO이름이 DTO로 끝나는 클래스만 선택
BaseObject+클래스 이름 뒤에 '+'가 붙으면 해당 클래스로부터 파생된 모든 자식 클래스 선택,
인터페이스 이름 뒤에 '+'가 붙으면 해당 인터페이스를 구현한 모든 클래스 선택
  • 메소드 지정
표현식설명
*(..)모든 메소드 선택
update*(…)메소드명이 update로 시작하는 모든 메소드 선택
  • 매개변수 지정
표현식설명
(..)모든 매개변수
(*)반드시 1개의 매개변수를 가지는 메소드만 선택
(com.gil.demo.dto.MemberDTO)매개변수로 MemberDTO를 가지는 메소드만 선택. 꼭 풀패키지명을 명시해줘야 함
(!com.gil.demo.dto.MemberDTO)매개변수로 MemberDTO를 가지지 않는 메소드만 선택
(Integer,…)한개 이상의 매개변수를 가지되, 첫번째 매개변수의 타입이 Integer인 메소드만 선택
(Integer, *)반드시 두 개의 매개변수를 가지되, 첫번째 매개변수의 타입이 Integer인 메소드만 선택

Advice 종류

Spring에서는 프록시를 이용해서 메소드 호출 시점에 Aspect를 적용하기 때문에 구현 가능한 Advice의 종류는 다음과 같다.

  • Before Advice
    대상 객체의 메소드 호출 전에 공통 기능을 실행한다.

  • After Advice
    익셉션 발생 여부에 상관없이 대상 객체의 메소드 실행 후 공통 기능을 실행한다.
    try - catch - finally의 finally 블록과 비슷하다.

  • After Returning Advice
    대상 객체의 메소드가 익셉션 없이 정상적으로 실행된 이후에 공통 기능을 실행한다.

  • After Throwing Advice
    대상 객체의 메소드를 실행하는 도중 익셉션이 발생한 경우에 공통 기능을 실행한다.

  • Around Advice
    대상 객체의 메소드 실행 전, 후 또는 익셉션 발생 시점에 공통 기능을 실행하는데 사용된다.

이 중 Around Advice가 널리 사용되는데, 대상 객체의 메소드를 실행 하기 전/후, 익셉션 발생 시점 등 다양한 시점에 원하는 기능을 삽입할 수 있기 때문이다.
캐시 기능, 성능 모니터링 기능과 같은 Aspect를 구현할 때에는 Around Adivce를 주로 이용한다.


JoinPoint 인터페이스

어드바이스 메소드를 의미있게 구현하려면 클라이언트가 호출한 비즈니스 메소드의 정보가 필요하다.
이럴때 JoinPoint 인터페이스가 제공하는 API들을 사용한다.
예외가 발생하였을때, 예외가 발생한 메소드 이름 등을 기록

메소드설명
Signature getSignature()클라이언트가 호출한 메소드의 시그니처(리턴타입, 매개변수) 정보가 저장된 Signature 객체를 리턴
Object getTarget()클라이언트가 호출한 비즈니스 메소드를 포함하는 비즈니스 객체를 리턴
Object[] getArgs()
  • Sinature API
메소드설명
String getName()클라이언트가 호출한 메소드 이름 리턴
String toLongString()클라이언트가 호출한 비즈니스 메소드의 리턴타입, 이름, 매개변수(시그니처)를 패키지 경로까지 포함하여 리턴)
String toShortString()클라이언트가 호출한 메소드 시그니처를 축약한 문자열로 리턴
String getDeclaringTypeName()클라이언트가 호출한 메소드를 가지는 클래스 풀패키지명을 리턴

Spring AOP의 특징

Weaving

Advice를 핵심로직코드에 적용하는것을 의미하며 3가지 방식이 존재한다.
일반적으로 컴파일시와 클래스 로딩 시에 weaving하는 방식은 AspectJ 라이브러리를 추가하여 구현할때 사용된다.

  1. Compile-time Weaving
    Load-time에 대한 절차가 없어서 퍼포먼스 하락 없이 구성이 가능하다.
    Lombok과 같이 compile시 간섭하는 plugin들과 충돌이 발생한다.

  2. Class Load-time Weaving
    applicationContext에 로드된 객체들을 불러온 뒤, AspectJ weaver에 의해 객체들을 weaving한다.
    객체들을 전부 불러온 뒤 weaving을 하기 때문에 약간의 퍼포먼스 하락이 있다.

  3. Run-time weaving
    Spring AOP에서 사용하는 방식으로 소스코드나 클래스 정보 자체를 변경하지 않고 중간에 프록시 객체를 생성하여 AOP를 적용한다.

스프링은 Aspect의 적용 대상(Target)이 되는 객체에 대한 Proxy를 만들어 제공한다.
대상 객체(Target)를 사용하는 코드는 대상 객체(Target)를 Proxy를 통해서 간접적으로 접근하게 되며, Proxy는 공통기능(Advice)을 실행한 뒤 대상객체(Target)의 실제 메서드를 호출하거나 또는 대상객체(Target)의 실제 메소드가 호출된 뒤 공통기능(Advice)을 실행한다.

Proxy

Proxy는 타겟을 감싸서 요청을 대신 받아주는 랩핑 클래스이다.
Spring에서는 Proxy를 이용해 객체지향의 5대원칙 중 하나인 OCP를 적용하고 있다.
Open-Close Principal : 개방폐쇄의 원칙
'소프트웨어 개체(클래스, 모듈, 함수 등등)는 확장에 대해 열려 있어야 하고,
수정에 대해서는 닫혀 있어야 한다.'는 프로그래밍 원칙

AOP에서 Spring Bean에 Proxy 주입

빈 후처리기들 중에서 자동으로 프록시를 생성하기 위해 DefaultAdvisorAutoProxyCreator라는 클래스를 사용한다.
이 클래스는 어드바이저를 이용한 자동 프록시 생성기이다. 빈 오브젝트의 일부를 프록시로 포장하고, 프록시를 빈으로 대신 등록시킬 수 있다.

DefaultAdvisorAutoProxyCreator 빈 후처리가 등록되어 있다면, 스프링은 빈 오브젝트를 만들 때마다 후처리기에게 빈을 보낸다.

  1. 후처리기는 빈으로 등록된 모든 어드바이저 내의 포인트컷을 이용해 전달받은 빈이 프록시 적용 대상인지 확인한다.
  2. 프록시 적용 대상이면 내장된 프록시 생성기를 통해 현재 빈에 대한 프록시를 생성하고 어드바이저를 연결한다.
  3. 프록시가 생성되면 전달받은 Target Bean 오브젝트 대신에 Proxy 오브젝트를 스프링 컨테이너에게 돌려준다.
  4. 컨테이너는 빈 후처리가 돌려준 Proxy 오브젝트를 빈으로 등록한다.

이 후처리기를 통해 일일이 ProxyFactoryBean을 빈으로 등록하지 않아도 여러 타깃 오브젝트에 자동으로 프록시를 적용시킬 수 있다.

AOP 설정 방법(로그 기능 예제)

아래 내용은 AOP(Aspect Oriented Programming, 관점 지향 프로그래밍), 저장로그 예제 [더블에스 devlog]님의 블로그 내용이 출처임을 밝힙니다.

  • pom.xml 에 라이브러리 추가
<dependency>
 <groupId>org.aspectj</groupId>
 <artifactId>aspectjweaver</artifactId>
 <version>1.8.9</version>
</dependency>
  • servlet-context.xml의 Namespace에 aop 추가

  • servlet-context.xml에 aop 태그 추가
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
  • AOP 기능을 지원할 Advice 클래스 작성
@Component // 스프링에서 관리하는 bean
@Aspect // AOP bean
public class LogAdvice {

 // private : 외부에서 로그를 가로채지 못하도록 하기 위해
 // static final : 로그 내용이 바뀌지 않으므로
 // 로깅툴을 사용하는 이유 : sysout명령어는 IO리소스를 많이 사용하여 시스템이 느려질 수 있다, 로그를 파일로 저장하여 분석할 필요가 있다.
 private static final Logger logger = LoggerFactory.getLogger(LogAdvice.class);

 // PointCut - 실행 시점
 // @Before, @After, @Around
 // 컨트롤러, 서비스, DAO의 모든 method를 실행 전후에 logPrint method가 자동으로 실행된다.
 // .. : 하위의 모든 디렉토리를 의미
 // *(..) : * - 하위의 모든 메서드, (..) - 모든 매개변수
 @Around("execution(* com.example.spring02.controller..*Controller.*(..))"
         + " or execution(* com.example.spring02.service..*Impl.*(..))"
         + " or execution(* com.example.spring02.model..dao.*Impl.*(..))")
 public Object logPrinnt(ProceedingJoinPoint joinPoint) throws Throwable{
     // 실행 시간 체크 : 시작시간
     long start = System.currentTimeMillis();
     // 핵심로직으로 이동
     Object result = joinPoint.proceed();
     // 클래스 이름
     String type = joinPoint.getSignature().getDeclaringTypeName();
     String name = "";
     if (type.indexOf("Controller") > -1) {
         name = "Controller:";
     } else if (type.indexOf("Service") > -1) {
         name = "ServiceImpl:";
     } else if (type.indexOf("DAO") > -1) {
         name = "DAO:";
     }
     // 메서드 이름
     logger.info(name+type+"."+joinPoint.getSignature().getName()+"()");
     // 파라미터 이름
     logger.info(Arrays.toString(joinPoint.getArgs()));
     // 실행 시간 체크 : 종료시간
     long end = System.currentTimeMillis();
     // 실행 시간 체크 : 연산
     long time = end-start;
     logger.info("실행 시간:"+time);
     return result;
 }
}

🙆‍♂️ 참고사이트 🙇‍♂️

Spring AOP의 이해[구루비]

스프링(Spring) 개발 - (16) AOP 설정하기 (부제: Controller에도 AOP 적용하기)[흔한 개발자의 개발 노트]

스프링 AOP 개념 및 Proxy를 이용한 구동원리 Posted by Jun Young on

Spring AOP, Proxy[만능 개발자 SKaSha]

profile
🚀 기록보단 길록을 20.10 ~ 22.02 ⭐ Move To : https://gil-log.github.io/

2개의 댓글

comment-user-thumbnail
2022년 8월 26일

좋은 글 감사합니다!

답글 달기
comment-user-thumbnail
2022년 12월 26일

좋은 글 감사합니다!

답글 달기