ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 스프링 AOP - 토비의 스프링 3.1 6장 끝
    Spring/Spring AOP 2020. 3. 8. 16:45
    반응형

    스프링 AOP

     

    자동 프록시 생성

    중복 문제의 접근 방법

    JDK의 다이내믹 프록시는 특정 인터페이스를 구현한 오브젝트에 대해서 프록시 역할을 해주는 클래스를 런타임 시 내부적으로 만들어 준다. 런타임 시에 만들어져 사용되기 때문에 클래스 소스가 따로 남지 않을 뿐이지 타깃 인터페이스의 모든 메소드를 구현하는 클래스가 분명히 만들어진다. 

     

    변하지 않는 타깃으로의 위임과 부가기능 적용 여부 판단이라는 부분은 코드 생성기법을 이용하는 다이내믹 프록시 기술에 맡기고, 변하는 부가기능 코드는 별도로 만들어서 다이내믹 프록시 생성 팩토리에 DI로 제공하는 방법을 사용한 것이다. 좀 독특하긴 하지만 변하는 로직과 변하지 않는 기계적인 코드를 잘 분리했다. 의미 있는 부가기능 로직인 트랜잭션 경계설정은 코드로 만들게 하고, 기계적인 코드인 타깃 인터페이스 구현과 위임, 부가기능 연동 부분은 자동생성하게 한 것이다.

     

    문제 

    첫째, 반복적인 프록시의 메소드 구현을 코드 자동생성 기법을 이용해 해결했담녀 반복적인 ProxyFactoryBean 설정 문제는 설정 자동드록 기법으로 해결할 수 없을까? 

    둘째, 실제 빈 오브젝트가 되는 것은 ProxyFactoryBean을 통해 생성되는 프록시 그 자체이므로 프록시가 자동으로 빈으로 생성되게 할 수 없을까?

    결론

    지금동안 살펴본 방법에서는 한번에 여러개의 빈에 프록시를 적용할 만한 방법은 없었다.

     

    빈 후처리기를 이용한 자동 프록시 생성기

    스프링은 OCP의 가장 중요한 요소인 유연한 확장이란느 갠며을 스프링 컨테이너 자신에게도 다양한 방법으로 적용하고 있다. 스프링의 DI를 리용해 만들어지는 애플리케이션의 코드가 OCP에 충실할 수 있다면 스프링 스스로도 그런 가치를 따르는게 마땅하다. 그래서 스프링은 컨테이너로서 제공하는 기능 중에서 변하지 않는 핵심적인 부분 외에는 대부분 확장할 수 있도록 확장 포인트를 제공해 준다.

     

    그 중에서 관심을 가질 만한 확장 포인트는 바로 BeanPostProcessor 인터페이스를 구현해서 만드는 빈 후처리기다. 빈 후처리기는 이름 그대로 스프링 빈 오브젝트로 만든어지고 난 후에, 빈 오브젝트를 다시 가공할 수 있게 해준다.

     

    DefaultAdvisorAutoProxyCreator 빈 후처리기가 등록되어 있으면 스프링은 빈 오브젝트를 만들 때마다 후처리기에게 빈을 보낸다. DefaultAdvisorAutoProxyCreator는 빈으로 등록된 모든 어드바이저 내의 포인트컷을 이용해 전달받은 빈이 프록시 적용 대상인지 확인한다. 프록시 적용 대상이면 그때는 내장된 프록시 생성기에게 현재 빈에 대한 프록시를 만들게 하고, 만들어진 프록시에 어드바이저를 연결해준다. 빈 후처리기는 프록시가 생성되면 원래 컨테이너가 전달해준 빈 오브젝트 대신 프록시 오브젝트를 컨테이너에게 돌려준다. 컨테이너는 최종적으로 빈 후처리기가 돌려준 오브젝트를 빈으로 등록하고 사용한다.

     

    빈 후처리기를 이용한 프록시 자동생성

    적용할 빈을 선정하는 로직이 추가된 포인트 컷이 담긴 어드바이저를 등록하고 빈 후처리기를 사용하면 일일이 ProxyFactoryBean 빈을 등록하지 않아도 타깃 오브젝트에 자동으로 프록시가 적용되게 할 수 있다.

     

     

    확장된 포인트컷

    포인트 컷은 클래스 필터와 메소드 매처 두 가지를 돌려주는 메소드를 갖고 있다. 실제 포인트 컷의 선별 로직은 이 두 가지 타입의 오브젝트에 담겨 있다.

    public interface Pointcut {
        ClassFilter getClassFilter(); // 프록시를 적용할 클래스인지 확인해준다.
        MethodMatcher getMethodMatcher(); // 어드바이스를 적용할 메소드인지 확인해준다.
    }

    Pointcut 선정 기능을 모두 적용한다면 먼저 프록시를 적용할 클래스인지 판단하고 나서, 적용 대상 클래스인 경우에는 어드바이스를 적용할 메소드인지 확인하는 식으로 동작한다. 클래스 자체가 프록시 적용 대상이 아니라면 어드바이스를 통한 부가기능 부여는 물 건너간 셈이다. 결국 이 두 가지 조건이 모두 충족되는 타깃의 메소드에 어드바이스가 적용되는 것이다.

     

    DefaultAdvisorAutoProxyCreator의 적용

    클래스 필터를 적용한 포인트컷 작성

    메소드 이름만 비교하던 포인트컷인 NameMatchMethodPointcut을 상속해서 프로퍼티로 주어진 이름 패턴을 가지고 클래스 이름을 비교하는 ClassFilter를 추가하도록 한다.

    package springbook.learningtest.jdk.proxy;
    //...
    public class NameMatchClassMethodPointcut extends NameMatchMethodPointcut {
        public void setMappedClassName(String mappedClassName) {
            // 모든 클래스를 다 허용하던 디폴트 클래스 필터를 프로퍼티로 
            // 받은 클래스 이름을 이용해서 필터를 만들어 덮어씌운다.
            this.setClassFilter(new SimpleClassFilter(mappedClassName));
        }
        
        static Class SimpleClassFilter implements ClassFilter {
            String mappedName;
            
            private SimpleClassFilter(String mappedName) {
                this.mappedName = mappedName;
            }
            
            public boolean matches(Class<?> clazz) {
            	// simpleMatch 와이들카드(*)가 들어간 문자열 비교를 지원하는 스프링의 유틸리티 메소드다. *
                // *name, *name*, *nasme* 세 가지 방식을 모두 지원
                return PatternMatchUtils.simpleMatch(mappedName, clazz.getSimpleName());
            }
        }
    }

     

     

    어드바이저를 이용하는 자동 프록시 생성기 등록

    자동 프록시 생성기인 DefaultAdvisorAutoProxyCreator는 등록된 빈 중에서 Advisor 인터페이스를 구현한 것을 모두 찾는다. 그리고 생성되는 모든 빈에 대해 어드바이저의 포인트컷을 적용해보면서 프록시 적용 대상을 선정한다. 빈 클래스가 프록시 선정 대상이라면 프록시를 만들어서 원래 빈 오브젝트와 바꿔치기한다. 원래 빈 오브젝트는 프록시 뒤에 연결돼서 프록시를 통해서만 접근 가능하게 바뀌며, 타깃 빈에 의존한다고 정의한 다른 빈들은 등록시 프록시 오브젝트를 대신 DI 받게 된다.

    DefaultAdvisorAutoProxyCreator 등록은 다음 한 줄과 같다.

    <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" />

     

     

    포인트컷 표현식을 이용한 포인트컷

    스프링은 아주 간단하고 효과적인 방법으로 포인트컷의 클래스와 메소드를 선정하는 알고리즘을 작성할 수 있는 방법을 제공한다. 정규식이나 JSP의 EL과 비슷한 일종의 표현식 언어를 사용해서 포인트컷을 작성할 수 있도록 하는 방법이다. 그래서 이것을 포인트컷 표현식(pointcut expression)이라고 부른다.

     

    포인트컷 표현식

    포인트컷 표현식을 지원하는 포인트컷을 적용하려면 AspectJExpressionPointcut 클래스를 사용하면 된다. Pointcut 인터페이스를 구현해야 하는 스프링의 포인트컷은 클래스 선정을 위한 클래스 필터와 메소드 선정을 위한 메소드 매처 두 가지를 각각 제공해야 한다.

     

    하지만 AspectJExpressionPointcut은 클래스와 메소드의 선정 알고리즘을 포인트컷 표현식을 이용해 한 번에 지정할 수 있게 해준다. 포인트컷 표현식은 자바의 RegEx 클래스가 지원하는 정규식처럼 간단한 문자열로 복잡한 선정조건을 쉽게 만들어낼 수 있는 강력한 표현식을 지원한다. 사실 스프링이 사용하는 포인트컷 표현식은 AspectJ라는 유명한 프로엠워크에서 제공하는 것을 가져와 일부 문법을 확장해서 사용하는 것이다.

     

    포인트컷 표현식을 이용하는 포인트컷 적용

    포인트컷 표현식은 메소드의 시그니처를 비교하는 방식인 execution() 외에도 몇 가지 표현식 스타일을 갖고 있다. 대표적으로 스프링에서 사용될 때 빈의 이름으로 비교하는 bean()이 있다.

     

     특정 애노테이션이 타입, 메소드, 파라미터에 적용되어 있는 것을 보고 메소드를 선정하게 하는 포인트컷도 만들 수 있다. 애노테이션만 부여해놓고, 포인트컷을 통해 자동으로 선정해서, 부가기능을 제공하게 해주는 방식은 스프링 내에서도 애용되는 편리한 방법이다.

     

    클래스 이름은 ServiceImpl로 끝나고 메소드 일므은 upgrade로 시작하는 모든 클래스에 적용되도록 하는 표현식을 만들고 이를 적용한 빈 설정은 다음과 같다.

    <bean id="transactionPointcut" class="org.springframework.aop.aspectj.AspectJExpressionPointcut">
    	<property name="expression" value"execution(* * .. *ServiceImpl.upgrade*(..))" />
    </bean>

     

     

    AOP란 무엇인가?

     

    내가 정리한 과정을 보게 된다면

    1) 트랜잭션 서비스 추상화(501p)

    2) 프록시와 데코레이터 패턴(501p)

    3) 다이내믹 프록시와 프록시 빈(502p)

    4) 자동 프록시 생성 방법과 포인트 컷(502p)

    5) 부가기능의 모듈화(503p)

    위의 흐름대로 따라갈 것이다...

     

    자 이제 AOP가 무엇인지 알아보자..

     

    AOP : 애스팩트(aspect) 지향 프로그래밍 

    부가기능 모듈화 작업은 기존의 객체지향 설계 패러다임과는 구분되는 새로운 특성이 있다고 생각했다. 그래서 이런 부가기능 모듈을 객체지향 기술에서는 주로 사용하는 오브젝트와는 다르게 특별한 이름으로 부르기 시작했다. 그것이 바로 애스펙트(aspect)다. 애스펙트란 그 자체로 애플리케이션의 핵심기능을 담고 있지는 않지만, 애플리케이션을 구성하는 중요한 한 가지 요소이고, 핵심기능에 부가되어 의미를 갖는 특별한 모듈을 가리킨다.

     

    애스팩트는 부가될 기능을 정의한 코드인 어드바이스와, 어드바이스를 어디에 적용할지를 결정하는 포인트컷을 갖고 있다. 

     

    독립 에스팩트를 이용한 부가기능의 분리와 모듈화

    위 그림은 이렇게 핵심기능 코드 사이에 침투한 부가기능을 독립적인 모듈인 애스팩트로 구분해낸 것이다. 2차원적인 평면 구조에서는 어떤 설계 기법을 동원해도 해결할 수 없었던 것을, 3차원 다면체 구조로 가져가면서 각각 성격이 다른 부가기능은 다른 면에 존재하도록 만들었다. 이렇게 독립된 측면에 존재하는 애스펙트로 분리한 덕에 핵심기능은 순수하게 그 기능을 담은 코드로만 조냊하고 독립적으로 살펴볼 수 있도록 구분된 면에 존재하는 것이다.

     

    이렇게 애플리케이션의 핵심적인 기능에서 부가적인 기능을 분리해서 애스펙트라는 독특한 모듈로 만들어서 설계하고 개발하는 방법을 애스펙트 지향 프로그래밍(Aspect Oriented Programming) 또는 약자로 AOP라고 부른다.

     

    AOP는 OOP를 돕는 보조적인 기술이지 OOP를 완전히 대체하는 새로운 개념은 아니다. AOP는 애스펙트를 분리함으로써 핵심기능을 설계하고 구현할 때 객체지향적인 가치를 지킬 수 있도록 도와주는 것이라고 보면 된다. AOP를 관점 지향 프로그래밍이라고도 한다. 

     

     

    AOP 적용기술

    프록시를 이용한 AOP

    스프링 AOP의 부가기능을 담은 어드바이스가 ㅈ거용되는 대상은 오브젝트의 메소드다. 프록시 방식을 사용했기 때문에 메소드 호출 과정에서 참여해서 부가기능을 제공해주게 되어있다. 어드바이스가 구현하는 MethodInterceptor 인터페이스는 다이내믹 프록시의 InvocationHandler와 마찬가지고 프록시로부터 메소드 요청정보를 전달받아서 타깃 오브젝트의 메소드를 호출한다.

     

    독립적으로 개발한 부가기능 모듈을 다양한 타깃 오브젝트의 메소드에 다이내믹하게 적용해주기 위해 가장 중요한 역활을 맡고 있는게 바로 프록시이다. 그래서 스프링 AOP는 프록시 방식의 AOP라고 할 수 있다.

     

    바이트코드 생성과 조작을 통한 AOP

    프록시 방식이 아닌 AOP도 있다. AOP 기술의 원조이자, 가장 강력한 AOP 프레임워크로 꼽히는 AspectJ는 프록시를 사용하지 않는 대표적인 AOP 기술이다. AspectJ는 스프링처럼 다이내믹 프록시 방식을 사용하지 않는다.

     

    AspectJ는 프록시처럼 간접적인 방법이 아니라, 타깃 오브젝트를 뜯어고쳐서 부가기능을 직접 넣어주는 직접적인 방법을 사용한다. 컴파일된 타깃의 클래스 파일 자체를 수정하거나 클래스가 JVM에 로딩되는 시점을 가로채서 바이트코드를 조작하는 복잡한 방법을 사용한다.

     

    AspectJ가 프록시 같은 방법이 있지만 컴파일된 클래스 파일 수정이나 바이트코드 조작과 같은 복잡한 방법을 사용하는 것에는 두 가지 이유가 있다.

     

    1. 바이트코드를 조작해서 타깃 오브젝트를 직접 수정해버리면 스프링과 같은 DI 컨테이너의 도움을 받아서 자동 프록시 생성 방식을 사용하지 않아도 AOP를 적용할 수 있기 때문이다.
    2. 프록시 방식보다 훨씬 강력하고 유연한 AOP가 가능하다. 바이트코드를 직접 조작해서 AOP를 적용하면 오브젝트의 생성, 필드 값이 조회와 조작, 스태틱 초기화 등의 다양한 작업에 부가기능을 부여해줄 수 있다. 

    AOP의 용어

    • 타깃
      • 타깃은 부가기능을 부여할 대상이다. 핵심기능을 담은 클래스일 수도 있지만 경우에 따라서는 다른 부가기능을 제공하는 프록시 오브젝트일 수도 있다.
    • 어드바이스
      • 어드바이스는 타깃에게 제공할 부가기능을 담은 모듈이다. 어드바이스는 오브젝트를 정의하기도 하지만 메소드 레벨에서 정의할 수도 있다
      • MethodInterceptor처럼 메소드 호출 과정에 전반적으로 참여하는 것도 잇지만 예외가 발생했으 ㄹ때문 동자갛는 어드바이스처럼 메소드 호출 과정의 일부에서만 동작하는 어드바이스도 있다.
    • 조인 포인트(Join Point)
      • 조인 포인트란 어드바이스가 적용될 수 있는 위치를 말한다. 스프링의 프록시 AOP에서 조인포인트는 메소드의 실행 단계뿐이다. 타깃 오브젝트가 구현한 인터페이스의 모든 메소드는 조인포인트가 된다.
    • 포인트컷
      • 어드바이스를 적용할 조인 포인트를 선별하는 작업 또는 그 기능을 정의한 모듈을 말한다. 스프링 AOp의 조인 포인트는 메소드의 실행이므로 스프링의 포인트컷은 메소드를 선정하는 기능을 갖고 있따. 그래서 포인트컷 표현식도 메소드의 실행이라는 의미인 excution으로 시작하고, 메소드의 시그니처를 비교하는 방법을 주로 이요한다. 메소드는 클래스 안에 조냊하는 것이기 때문에 메소드 선정이란 결국 클래스를 선정하고 그 안의 메소드를 선정하는 과정을 거치게 된다.
    • 프록시
      • 클라이언트와 타깃 사이에 조냊하면서 부가기능을 제공하는 오브젝트이다. DI를 통해 타깃 대신 클라이언트에게 주입되며, 클라이언트의 메소드 호출을 대신 받아서 타깃에 위임해주면서 그 과정에서 부가기능을 부여한다. 스프링은 프록시를 이용해 AOP를 지원한다.
    • 어드바이저
      • 포인트커소가 어드바이스를 하나씩 갖고 있는 오브젝트이다. 어드바이저는 어던 부가기능(어드바이스)을 어디에(포이트컷) 전달할 것이가를 알고 있는 AOP의 가장 기본이 되는 모듈이다. 스프링은 자동 프록시 생성기가 어드바이저를 AOP 작업의 정보로 활용한다. 어드바이저는 스프링 AOP에서만 사용되는 특별한 요엉이고, 일반적인 AOP에서는 사용되지 않는다.
    • 애그펙트
      • OOP의 클래스와 마찬가지로 애스팩튼는 AOP의 기본 모듈이다. 한 개 똔느 그 이상의 포인트컷과 어드바이스의 조합으로 ㅁ나들어지며 보통 싱글톤 형태의 오브젝트로 존재한다. 따라서 크랠스와 같은 모듈 정의와 오브젝트와 같은 실체(인스턴스)의 구분이 특별이 없다. 두 가지 모두 에스팩트라 불린다. 스프링의 어드바이저는 아주 단순한 애스펙트라고 볼 수 있다.

     

    트랜잭션 속성

    트랜잭션이라고 모두 같은 방식으로 동작하는 것이 아니다. DefaultTransactionDefinition이 구현하고 있는 TransactionDefinition 인터페이스는 트랜잭션 동작방식에 영향을 줄 수 있는 4가지 속성을 정의하고 있다.

     

    • 트랜잭션 전파 트랜잭션의 경계에서 이미 진행 중인 트랜잭션이 있을 때 또는 없을 때 어떻게 동작할 것인가를 결정하는 방식
    • 격리수준 모든 DB 트랜잭션은 격리수준(isolation level)을 갖고 있다. 격리수준은 기본적으로 DB에 설정되어 있지만 JDBC 드라이버나 DataSource 등에서 재설정할 수 있고, 필요하다면 트랜잭션 단위로 격리수준을 조정할 수 있다.
    • 제한시간 트랜잭션을 수행하는 제한시간(timeout)을 설정할 수 있다. DefaultTransactionDefinition의 기본 설정은 제한시간이 없다.
    • 읽기전용 읽기전용(read only)으로 설정해두면 트랜잭션 내에서 데이터를 조작하는 시도를 막아줄 수 있다. 또한 데이터 액세스 기술에 따라서 성능이 향상될 수도 있다.

    포인트 컷과 트랜잭션 속성의 적용 전략

    • 트랜잭션 포인트컷 표현식은 타입 패턴이나 빈 이름을 이용한다.
    • 공통된 메소드 이름 규칙을 통해 최소한의 트랜잭션 어드바이스와 속성을 정의한다.
    • 프록시 방식 AOP는 같은 타깃 오브젝트 내의 메소드를 호출할 때는 적용되지 않는다.

     

    트랜잭션 속성 적용

    트랜잭션 경계설정의 일원화

    AOP를 이용해 코드 외부에서 트랜잭션의 기능을 부여해주고 속성을 지정 할 수 있게 하는 방법을 선언적 트랜잭션이라고 한다. 반대로 TransactionTemplate이나 개별 데이터 기술의 트랜잭션 API를 사용해 직접 코드 안에서 사용하는 방법은 프로그램에 의한 트랜잭션이라고 한다. 스프링은 이 두 가지 방법을 모두 지원하고 있다. 물론 특별한 경우가 아니라면 선언적 방식의 트랜잭션을 사용하는 것이 바람직하다.

     

     

    정리

    • 트랜잭션 경계설정 코드를 분리해서 별도의 클래스로 만들고 비즈니스 로직 클래스와 당일한 인터페이스를 구현하면 DI의 확장 기능을 이용해 클라이언트의 변경 없이도 깔끔하게 분리된 트랜잭션 부가기능을 만들 수 있다.
    • 트랜잭션처럼 환경과 외부 리소스에 영향을 받는 코드를 분리하면 비즈니스 로직에만 충실한 테스트를 만들 수 있다.
    • 목 오브젝트를 활용하면 의존관계 속에 있는 오브젝트도 손 쉽게 고립된 테스트로 만들 수 있다.
    • DI를 이용한 트랜잭션의 분리는 데코레이터 패턴과 프록시 패턴으로 이해될 수 있다.
    • 번거로운 프록시 클래스 작성은 JDK의 다이내믹 프록시를 사용하면 간단하게 만들 수 있다.
    • 다이내믹 프록시는 스태틱 팩토리 메소드를 사용하기 때문에 빈으로 등록하기 번거롭다. 따라서 팩토리 빈으로 만들어야 한다. 스프링은 자동 프록시 생성 기술에 대한 추상화 서비스를 제공하는 프록시 팩토리 빈을 제공한다.
    • 프록시 팩토리 빈의 설정이 반복되는 문제를 해결하기 위해 자동 프록시 생성기와 포인트 컷을 활용할 수 있다. 자동 프록시 생성기는 부가기능이 담긴 어드바이스를 제공하는 프록시를 스프링 컨테이너 초기화 시점에 자동으로 만들어준다.
    • 포인트컷은 AspectJ 포인트컷 표현식을 사용해서 작성하면 편리하다.
    • AOP는 OOP만으로는 모듈화하기 힘든 부가기능을 효과적으로 모듈화하도록 도와주는 기술이다.
    • 스프링은 자주 사용되는 AOP 설정과 트랜잭션 속성을 지정하는데 사용할 수 있는 전용 태그를 제공한다.
    • AOP를 이용해 트랜잭션 속성을 지정하는 방법에는 포인트컷 표현식과 메소드 이름 패턴을 이용하는 방법과 타깃에 직접 부여하는 @Transactional 애노테이션을 사용하는 방법이 있다.
    • @Transactgional을 이용한 트랜잭션 속성을 테스트에 적용하면 손쉽게 DB를 사용하는 코드의 테스트를 만들 수 있다.
    반응형

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

    Spring AOP - 프록시 기반 AOP  (0) 2020.03.30
    스프링의 프록시 팩토리 빈  (0) 2020.03.04
    다이나믹 프록시 (4)  (0) 2020.03.02
    다이나믹 프록시 (3)  (0) 2020.02.26
    다이나믹 프록시 (2)  (0) 2020.02.19
Designed by Tistory.