카샤의 만개시기

Spring AOP, Proxy 본문

Java/Spring

Spring AOP, Proxy

SKaSha 2019. 7. 4. 19:48

AOP는 관점지향 프로그래밍(Aspect Oriented Programming)으로써, 공통 기능(로깅, 트랜잭션, 접근 제어 등의 보안) 등에 대한 횡단 영역의 공통된 부분의 중복을 제거하고 모듈화 하는 프로그래밍 방식이다.

Aspect

공통으로 적용되는 기능

Advice

언제 Aspect을 로직에 적용할 지를 정의.

  • @Before
    메소드 실행 전 기능 수행.
  • @After
    메소드 결과와 상관없이 메소드가 완료 된 이후에 기능 수행.
  • @AfterReturning
    메소드가 성공적으로 완료 된 이후에 기능 수행.
  • @AfterThrowing
    메소드 수행 중 예외 발생 시 이후에 기능 수행.
  • @Around
    메소드가 실행되기 전과 후 기능 구행. proceed() 메소드 호출 전, 후를 통해 구분할 수 있다.

Joinpoint

Advice를 적용 가능한 지점을 의미. 메소드 호출, 필드값 변경 등이 Joinpoint에 해당 함.
Spring AOP에서는 프록시 기반의 AOP이기 때문에 메소드 실행에 대한 JoinPoint만 지원되며, 필드 값 변경 같은 Joinpoint를 사용하고 싶다면 AspectJ를 사용해야 한다.

Pointcut

Joinpoint의 부분집합으로서 실제로 Advice가 적용되는 Joinpoint를 나타낸다. 스프링에서는 정규 표현식이나 AspectJ의 문법을 이용하여 Poincut을 재정의 할 수 있다.

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를 적용한다.

Proxy

Proxy
타겟을 감싸서 요청을 대신 받아주는 랩핑 클래스이다.
Spring에서는 Proxy를 이용해 객체지향의 5대원칙 중 하나인 OCP(Open-Close Principal : 개방폐쇄의 원칙)을 적용하고 있다.

OCP (Open-Close Principal : 개방 폐쇄의 원칙)

개방-폐쇄 원칙(OCP, Open-Closed Principle)은 '소프트웨어 개체(클래스, 모듈, 함수 등등)는 확장에 대해 열려 있어야 하고, 수정에 대해서는 닫혀 있어야 한다.'는 프로그래밍 원칙이다.

AOP에서 Spring Bean에 Proxy 주입

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

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

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

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

스프링 AOP에서는 CGLIB Proxy, JDK Dynamic Proxy를 이용한 Run-time weaving 방식을 제공한다.

JDK Dynamic Proxy

JDK Dynamic Proxy는 JDK 1.3+ 부터 제공되는 Proxy Factory에 의해 런타임 시 동적으로 만들어 지는 오브젝트이다. JDK Dynamic Proxy는 반드시 Interface가 정의 되어있고, Interface에 대한 명세를 기준으로 Proxy를 생성한다. 따라서 Interface 선언에 대한 강제성이 있다는 단점이 있다.
내부적으로 Dynamic Proxy에서는 InvocationHandler라는 Interface를 구현하여 만들어지는데 InvocationHandler의 invoke라는 함수를 Override하여 Proxy의 위임 기능을 수행한다. 이 과정에서 Object에 대해 Reflection기능을 사용하여 기능을 구현하기 때문에 퍼포먼스의 하락의 원인이 되기도 한다.

public class ExamDynamicHandler implements InvocationHandler {
    private ExamInterface target; // 타깃 객체에 대한 클래스를 직접 참조하는것이 아닌 Interface를 이용

    public ExamDynamicHandler(ExamInterface target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        // TODO Auto-generated method stub
        // 메소드에 대한 명세, 파라미터등을 가져오는 과정에서 Reflection 사용
        String ret = (String)method.invoke(target, args); //타입 Safe하지 않는 단점이 있다.
        return ret.toUpperCase(); //메소드 기능에 대한 확장
    }
}

CGLIB Proxy

CGLIB Proxy는 순수 Java JDK 라이브러리를 이용하는 것이 아닌 CGLIB라는 외부 라이브러리를 추가해야만 사용할 수 있다. 실제 CGLIB의 Enhancer라는 클래스를 바탕으로 Proxy를 생성하며, JDK Dynamic Proxy의 단점인 Interface가 없어도 Proxy를 생성 할 수 있다. CGLIB Proxy는 Target Class를 상속받아 생성된다. 그렇기 때문에 개발자는 Proxy를 생성 하기 위해 굳이 Interface를 만들어야 하는 수고를 덜 수 있다.

하지만, 상속을 이용하는 만큼 final이나 private과 같이 상속에 대해 Override를 지원하지 않는 경우 Proxy에서 해당 메소드에 대한 Aspect를 적용할 수 없다는 단점이 있다.

CGLIB Proxy의 경우 실제 바이트 코드를 조작하여 JDK Dynamic Proxy보다는 퍼포먼스가 상대적으로 빠른 장점이 있다.
CGLIB

Spring boot 프로젝트 리더가 다음과 같이 언급했다.

We've generally found cglib proxies less likely to cause unexpected cast exceptions.
일반적으로 cglib가 예외를 발생시킬 가능성이 낮다는걸 발견하였다고 한다.

이로인해 Spring Boot는 AOP의 기본 전략을 CGLIB으로 변경하였다.

Spring AOP vs AspectJ

Spring AOP AspectJ
순수 자바로 구현 자바 프로그래밍 언어의 확장을 사용하여 구현
별도의 컴파일 프로세스 필요 없음 LTW가 설정되어 있지 않으면 AspectJ 컴파일러 (ajc)가 필요함
runtime weaving만 가능 runtime weaving 불가능. compile, post-compile, load time weaving 가능.
method weaving만 지원 fields, methods, constructors, static initializers, final class/methods 등의 weaving 지원
Spring 컨테이너에 의해 관리되는 bean에서만 구현 가능 모든 도메인 객체에서 구현 가능
메소드 실행 pointcuts 만 지원 모든 pointcuts 지원
대상 객체에 프록시가 생성되며 이를 통해 aspect가 적용 됨 어플리케이션이 실행되기 전에 aspect들이 코드에 직접 weaving 됨
AspectJ보다 느림 성능 향상
쉬움 복잡하고 어려움

Comparing Spring AOP and AspectJ

추가적으로 보면 좋은 글

http://wonwoo.ml/index.php/post/1576

참조

https://jaehun2841.github.io/2018/07/21/2018-07-21-spring-aop3/#jdk-dynamic-proxy
https://tram-devlog.tistory.com/entry/Spring-AOP-weaving-proxy

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

JPA에서 Optimistic Lock과 Pessimistic Lock  (0) 2019.07.08
RestTemplate  (2) 2019.07.07
Comments