본문 바로가기
Spring Framework/Spring

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

by 도쿠니 2022. 6. 7.

AOP란?

  • = Aspect Oriented Programming
  • 여러 객체에서 공통적으로 사용하고 있는 기능을 분리해서 모듈화하고 재사용하는 프로그래밍 기법
  • 핵심 기능공통 기능의 구현을 분리하여 핵심 기능의 코드 수정없이 공통 기능 적용 가능
  • 여러 클래스, 메서드에 공통적으로 나타나는 코드들을 Concern 이라한다.
  • 각 클래스에 있는 Crosscutting Concerns(흩어진 관심사)를 Aspect로 모듈화하고 핵심적인 비즈니스 로직에서 분리하여 재사용하겠다는 것이 AOP의 취지이다.
  • 대표적으로 인증, 트랜잭션 관리, 로깅 등과 같은 인프라 로직을 AOP로 처리한다.
  • 스프링프록시 기반 AOP를 지원한다.

AOP 적용 방식

  • 컴파일 시점에 코드에 공통기능을 삽입
    • AOP 개발 도구가 소스 코드를 컴파일 하기 전에 공통 구현 코드를 소스에 삽입하는 방식
    • 스프링 AOP에서 지원 X, AspectJ 같은 AOP 전용 도구를 사용해서 적용
  • 클래스 로딩 시점(로드 타임)에 바이트 코드에 공통 기능을 삽입
    • 클래스를 로딩할 때 바이트 코드에 공통 기능을 클래스에 삽입하는 방식
    • 스프링 AOP에서 지원 X, AspectJ 같은 AOP 전용 도구를 사용해서 적용
  • 런타임에 프록시 객체를 생성해서 공통 기능을 삽입
    • 프록시 객체를 생성하고 그 안에 실제 객체를 넣어 실제객체를 통해 핵심기능을 실행하고 공통 기능은 프록시 객체가 처리하는 방식
    • 스프링 AOP에서 지원
    • 스프링 AOP가 프록시 객체를 자동으로 생성하여 처리해주기 때문에 프록시 클래스를 직접 구현할 필요가 없다.
    • 개발자는 공통 기능을 구현한 클래스만 구현하면 된다.

 

AOP 프록시 동작 방식

핵심 기능을 가진 객체와 프록시와의 관계

  • 프록시는 실제 객체가 인터페이스를 상속하면 인터페이스를 이용해서 프록시를 생성합니다. (실제 객체의 하위 X)

AOP 용어

  • Aspect (관점,관심)
    • 여러 객체에 공통으로 적용되는 기능
    • 트랜잭션(@Transactional) 이나 캐시(@Cacheable), 보안 등 이 좋은 예
    • 클래스 단위에서 @Aspect 로 적용
    • Aspect 클래스 내부에는 Advice와 Point Cut이 들어간다.
  • Advice (조언)
    • AOP에서 실제로 적용하는 기능을 뜻함
    • 언제 공통 관심 기능을 핵심 로직에 적용할지를 정의
    • 실제로 적용할 기능을 작성한다.
    • 어노테이션에는 포인트컷을 알려줘야한다.
      • 종류
        • Before
          • 대상 객체의 메서드 호출 전에 공통 기능을 실행
        • After Returning
          • 대상 객체의 메서드가 익셉션 없이 실행된 이후에 공통기능을 실행
        • After Throwing
          • 대상 객체의 메서드를 실행하는 도중 익셉션이 발생한 경우에 공통기능을 실행
        • After
          • 익셉션 발생여부에 상관없이 대상객체의 메서드 실행 후 공통 기능 실행
          • try - catch -finally 에서 finally 와 유사
        • Around
          • 대상 객체 메서드 실행하기 전/후 , 익셉션 발생 시점 등 다양한 시점에 공통 기능 실행
          • 주로 사용한다!
  • Joinpoint (연결 포인트)
    • Advice를 적용 할 수 있는 지점, 실행될 수 있는 연결 포인트
    • 메서드 호출, 필드 값 변경 등
    • 스프링은 프록시를 이용해서 AOP를 구현하기 때문에 메서드 호출에 대한 Joinpoint만 지원한다.
  • Pointcut (포인트 선택방법)
    • 실제 Advice가 적용되는 Joinpoint
    • Joinpoint 중에서 해당 Aspect를 적용할 대상을 뽑을 조건식
    • 스프링에서는 정규표현식이나 AspectJ의 문법을 이용하여 Pointcut을 정의할 수 있다
    • 적용 대상을 의미한다.
    • 여러 Aspect에서 공통으로 사용하는 Pointcut이 있다면 별도 클래스로 분리하는 것이 좋다.
      • 분리한 Pointcut 클래스는 따로 빈으로 등록할 필요가 없다. 어차피 advice 어노테이션에서 해당 클래스에 접근 가능하다면 사용할 수 있기 때문.
  • Target
    • Advice가 적용될 대상 (클래스,메서드 등)
  • AOP Proxy
    • 대상 오브젝트에 Aspect를 적용하는 경우, Advice를 덧붙이기 위해 하는 작업을 의미
    • 주로 CGLIB(실행 중에 실시간으로 코드를 생성하는 라이브러리) 프록시를 사용하여 프록싱 처리한다.
  • Weaving
    • Advice를 핵심 로직 코드에 적용하는 것을 의미

 

AOP 예시 코드 - 기본

테스트 실행 파일 : AppRun.java

package com.dokuny.spring_study;

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 AppRun implements ApplicationRunner {

    @Autowired
    private Hello hello;

    @Autowired
    private World world;

    @Override
    public void run(ApplicationArguments args) throws Exception {

        hello.print();
        world.print();
    }
}
  • 스프링 부트 환경이기 때문에 테스트하기 위해서 ApplicationRunner 인터페이스를 구현하는 클래스를 작성했습니다.
  • 만약 부트환경이 아니라면, 설정 파일에 @EnableAspectJAutoProxy 를 붙여줘야합니다.

핵심 기능 클래스 : HelloWorld.interface, Hello.java, World.java

package com.dokuny.spring_study;

// 인터페이스
public interface HelloWorld {
    void print();
}
package com.dokuny.spring_study;

import org.springframework.stereotype.Component;

@Component
public class Hello implements HelloWorld{
    @Override
    public void print() {
        System.out.println("Hello!");
    }
}
package com.dokuny.spring_study;

import org.springframework.stereotype.Component;

@Component
public class World implements HelloWorld {

    @Override
    public void print() {
        System.out.println("World!");
    }
}
  • 핵심 기능을 가지고 있는 HelloWorld 인터페이스를 구현한 클래스들입니다.

Aspect 클래스 : TestAspect.java

package com.dokuny.spring_study;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

@Component
@Aspect
public class TestAspect {

    // PointCut 설정
    @Pointcut("execution(public void HelloWorld.print())")
    private void publicTaget() {}

    // Advice 설정
    @Around("publicTaget()")
    public Object measure(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("이전");
        Object result = joinPoint.proceed(); // 공통 기능 실행
        System.out.println("이후");
        return result;
    }
}
  • @Component
    • Aspect 클래스를 빈으로 등록해야하기 때문에 붙입니다.
  • @Aspect
    • 이 클래스가 Aspect 클래스임을 알려줍니다.
    • @EnableAspectJAutoProxy 를 가진 설정 파일이 @Aspect 를 가진 클래스를 찾아서 실행시킵니다.
  • @Pointcut
    • execution 명시자 표현식을 통해서 공통 기능을 적용할 Pointcut을 정의합니다. 
      • 스프링 AOP는 public 메소드에만 적용할 수 있기 때문에 수식어패턴은 public만 유의미합니다.
      • * 은 모든 값을 표현, .. 은 0개 이상이라는 뜻
      • execution(public 메서드 대상) 뿐만 아니라 within(특정 패키지 대상) 등도 사용 가능합니다.
      • 더불어, @Pointcut("포인트컷1 && 포인트컷2") 이런식으로도 사용 가능합니다.
    execution(수식어패턴? 리턴타입패턴 클래스이름패턴?메서드이름패턴(파라미터패턴))
    
  • @Around
    • Advice를 설정합니다. 어노테이션은 Advice 종류에서 선택해서 사용합니다.
    • @Around(”Pointcut Name()”) 형식으로 사용합니다.
      • 미리 작성한 포인트컷 대신 직접적으로 execution 표현식을 사용하여 포인트컷을 넣을 수 있습니다.
      • 어노테이션 방식으로도 적용 가능합니다. 이 부분은 후술하겠습니다.
  • ProceedingJoinPoint
    • Advice가 적용되는 대상을 의미합니다.
    • 즉, 적용 대상 메소드를 의미하며 이 객체를 통해 대상 메소드에 대한 정보를 가져올 수 있습니다.
      • Object proceed() : 실제 객체의 메소드를 실행합니다.
      • Signature getSignature() : 호출되는 메서드에 대한 정보를 구합니다.
      • Object getTarget() : 대상 객체를 구합니다.
      • Object[] getArgs() : 파라미터 목록을 구합니다
  • 실행 결과
이전
Hello!
이후
이전
World!
이후

Advice 적용 순서 지정하기

  • @Order 사용
    • 여러 Advice를 적용해야 한다면, @Aspect 가 붙은 클래스 단위에다가 @Order(숫자)를 추가합니다.
    • 숫자가 작을 수록 우선적으로 적용합니다.

어노테이션 방식 AOP

  • 어노테이션을 이용하면 어노테이션이 붙은 메서드만 적용시킬 수 있다.

커스텀 어노테이션 생성

package com.dokuny.spring_study;

import java.lang.annotation.*;

@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.CLASS)
public @interface CustomAspectAnno {
}
package com.dokuny.spring_study;

import org.springframework.stereotype.Component;
@Component
public class Hello implements HelloWorld{

		// 커스텀 어노테이션 적용
    @CustomAspectAnno
    @Override
    public void print() {
        System.out.println("Hello!");
    }
}
package com.dokuny.spring_study;

import org.springframework.stereotype.Component;

@Component
public class World implements HelloWorld {

		// 커스텀 어노테이션 적용
    @CustomAspectAnno
    @Override
    public void print() {
        System.out.println("World!");
    }
}
package com.dokuny.spring_study;

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 TestAspect {

    // @annotation(어노테이션이름)을 사용하여 어노테이션을 이용한다는 것을 알린다..
    @Around("@annotation(CustomAspectAnno)")
    public Object measure(ProceedingJoinPointjoinPoint) throws Throwable {
        System.out.println("이전");
        Objectresult=joinPoint.proceed(); // 공통 기능 실행
        System.out.println("이후");
        returnresult;
    }
}

'Spring Framework > Spring' 카테고리의 다른 글

Spring Validation  (0) 2022.06.07
Data Binding  (0) 2022.06.07
DI 정리  (0) 2022.06.06
Spring Framework 소개  (0) 2022.06.05
Spring 등장 배경  (0) 2022.06.02

댓글