본문 바로가기

Spring/토비의 스프링 3.1

6.5 스프링 AOP



작업의 목표

  1. 트랜잭션 코드를 깔끔하고 료과적으로 분리
  2. 트랜잭션 코드는 투명한 부가기능 형태로 제공
    • 기존 설계와 코드에 영향을 주지 않음

6.5.1 자동 프록시 생성

대부분의 문제는 해결함

  1. 타깃 코드는 깔끔한 채로 남아있음
  2. 부가기능은 한 번만 만들어 모든 타깃과 메서드에 재사용 가능
  3. 타깃의 적용 메소드를 선정하는 방식도 독립적으로 작성 가능

남은 문제

  1. 부가기능이 타깃 오브젝트마다 새로 만들어짐
    • ProxyFactoryBean의 어드바이스를 통해 해결
  2. 타깃 오브젝트마다 거의 비슷한 내용의 ProxyFactoryBean 설정정보를 추가해야함

중복 문제의 접근 방법

DAO

  • 바뀌는 부분과 바뀌지 않는 부분을 구분해서 분리
  • 템플릿과 콜백, 클라이어트를 분리

프록시 클래스 코드

  • 다이내믹 프록시라는 런타임 코드 자동생성 기법을 이용하여 해결
  • 변하지 않는 타깃으로의 위임과 부가기능 적용 여부는 다이내믹 프록시 기술에 맡김
  • 변하는 부가 기능 코드는 별도로 만들어서 다이내믹 프록시 생성 팩토리에 DI로 제공

일정한 타깃 빈의 목록을 제공하면 자동으로 각 타깃 빈에 대한 프록시를 만들어준다면?

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

빈 후처리기

  • 스프링 빈 오브젝트로 만들어 진 후 빈 오브젝트를 다시 가공
  • BeanPostProcessor 인터페이스를 구현

DefaultAdvisorAutoProxyCreator

  • 어드바이저를 이용한 자동 프록시 생성기
  • 스프링에 적용하는 방법 : 빈 후처리기 자체를 빈으로 등록
  • 빈 오브젝트가 생성될 때마다 빈 후처리기에 보내서 후처리 작업을 요청

워크플로우

  1. DefaultAdvisorAutoProxyCreator 빈 후처리기를 스프링 빈으로 등록
  2. 스프링이 빈 오브젝트를 생성할 때마다 후처리기에 빈을 보냄
  3. 포인트컷을 이용하여 전달받은 빈이 프록시 적용 대상인지를 확인
  4. 적용 대상일 경우 프록시 생성기에게 현재 빈에 대한 프록시를 만들게 하고, 만들어진 프록시에 어드바이저를 연결

확장된 포인트컷

포인트컷의 기능

  1. 클래스 필터
  2. 메소드 매처
public interface Pointcut {
    // 프록시를 적용할 클래스인지 확인
    ClassFilter getClassFilter();
    // 어드바이스를 적용할 메소드인지 확인
    MethodMatcher getMethodMatcher();
}

지금까지는 메소드 매처 기능만 사용

  • NameMatchMethodPointcut은 클래스 필터 기능이 없음

포인트컷 테스트

포인트컷 학습 테스트

  1. NameMatchMethodPointcut을 확장하여 클래스 필터를 구현
  2. 프록시 적용 후보 클래스를 여러 개 만듦
  3. 포인트컷을 적용한 ProxyFactoryBean으로 프록시를 만들도록 함
  4. 어드바이스 적용 여부 확인

DynamicProxyTest.java

public class DynamicProxyTest {
    /**
     * 확장 포인트컷 테스트
     */
    @Test
    public void classNamePointcutAdvisor() {
        // 포인트컷 준비
        // 익명 내부 클래스 활용
        NameMatchMethodPointcut classMethodPointcut = new NameMatchMethodPointcut() {
            private static final long serialVersionUID = 1L;

            public ClassFilter getClassFilter() {
                return new ClassFilter() {
                    public boolean matches(Class<?> clazz) {
                        // 클래스 이름이 HelloT로 시작하는 것만 선정
                        return clazz.getSimpleName().startsWith("HelloT");
                    }
                };
            }
        };
        // sayH로 시작하는 메소드 이름을 가진 메소드를 선정
        classMethodPointcut.setMappedName("sayH*");

        // 테스트
        checkAdviced(new HelloTarget(), classMethodPointcut, true); // 적용 클래스

        class HelloWorld extends HelloTarget {};
        checkAdviced(new HelloWorld(), classMethodPointcut, false); // 적용 클래스 아님

        class HelloToby extends HelloTarget {};
        checkAdviced(new HelloToby(), classMethodPointcut, true); // 적용 클래스
    }

    private void checkAdviced(Object target, Pointcut pointcut, boolean adviced) {
        ProxyFactoryBean pfBean = new ProxyFactoryBean();
        pfBean.setTarget(target);
        pfBean.addAdvisor(new DefaultPointcutAdvisor(pointcut, new UppercaseAdvice()));
        Hello proxiedHello = (Hello) pfBean.getObject();

        // 적용 대상 여부
        if (adviced) {
            // 메소드 선정 방식을 통해 어드바이스 적용
            assertThat(proxiedHello.sayHello("Toby"), is("HELLO TOBY"));
            assertThat(proxiedHello.sayHi("Toby"), is("HI TOBY"));
            assertThat(proxiedHello.sayThankYou("Toby"), is("Thank You Toby"));
        } else {
            // 어드바이스 적용 대상 후보에서 아예 탈락
            assertThat(proxiedHello.sayHello("Toby"), is("Hello Toby"));
            assertThat(proxiedHello.sayHi("Toby"), is("Hi Toby"));
            assertThat(proxiedHello.sayThankYou("Toby"), is("Thank You Toby"));
        }
    }
}

classMethodPointcut(포인트컷)

  1. NameMatchMethodPointcut을 내부 익명 클래스 방식으로 확장해서 만듦
    • ClassFilter()를 오버라이딩해서 HelloT로 시작하는 클래스만 선정
    • 메소드 이름 선정기준은 기존 것을 그대로 유지
  2. 테스트 대상 (세가지)
    • HelloTarget을 상속(내용은 같음)
    • 클래스 이름이 다름
  3. HelloWorld 클래스는 적용 대상에서 제외됨(메서드 이름과 무관)

6.5.2 DefaultAdvisorAutoProxyCreator의 적용

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

NameMatchMethodPointcut 상속 후 ClassFilter를 추가

public class NameMatchClassMethodPointCut extends NameMatchMethodPointcut {
    private static final long serialVersionUID = 1L;

    public void setMapperClassName(String mappedClassName) {
        // 모든 클래스를 다 허용하는 디폴트 클래스 필터를 프로퍼티로 받은 클래스 이름을 이용해서 필터를 만들어 덮어씌운다.
        this.setClassFilter(new SimpleClassFilter(mappedClassName));
    }

    static class SimpleClassFilter implements ClassFilter {
        String mappedName;

        private SimpleClassFilter(String mappedName) {
            this.mappedName = mappedName;
        }

        @Override
        public boolean matches(Class<?> clazz) {
            // 와일드카드가 들어간 문자열 비교를 지원하는 스프링의 유틸리티 메소드
            return PatternMatchUtils.simpleMatch(mappedName, clazz.getSimpleName());
        }
    }
}

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

DefaultAdvisorAutoProxyCreator

  • 등록된 빈 중 Advisor 인터페이스를 구현한 것은 모두 찾음
  • 생성되는 모든 빈에 대해 포인트컷을 적용해보면서 프록시 적용 대상을 선정
  • 선정 대상이라면 원래 빈 오브젝트와 바꿔치기 함

타깃 빈에 의존한다고 정의한 다른 빈들은 프록시 오브젝트를 대신 DI 받음

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

id 애트리뷰트가 없고 class만 있음

  • 다른 빈에서 참조되거나 코드에서 빈 이름으로 조회될 필요가 없는 빈은 아이디를 등록하지 않아도 무관

포인트컷 등록

기존의 포인트컷 설정을 삭제하고 새로 만든 클래스 필터 지원 포인트컷을 빈으로 등록

<bean id="transactionPointCut" class="com.david.tobysspring.user.service.NameMatchClassMethodPointcut">
    <property name="mappedClassName" value="*ServiceImpl" />
    <property name="mappedName" value="upgrade*" />
</bean>

어드바이스와 어드바이저

transactionAdvice 빈 설정은 수정할 필요 없음

  • 하지만 어드바이저로서 사용되는 방법은 바뀌었음

ProxyFactoryBean 제거와 서비스 빈의 원상복구

userServiceImpl 빈의 아이디를 userService로 되돌려 놓을 수 있음

  • 명시적인 프록시 팩토리 빈을 등록하지 않기 때문

ProxyFactoryBean 타입의 빈 삭제

<bean id="userService" class="com.david.tobysspring.user.service.UserServiceImpl">
    <property name="userDao" ref="userDao" />
    <property name="mailSender" ref="mailSender" />
</bean>

프록시를 적용하기 전의 단순한 상태로 원상복귀

자동 프록시 생성기를 사용하는 테스트

Autowired를 통해 컨텍스트에 가져오는 UserService 타입 오브젝트는 UserServiceImpl 오브젝트가 아닌 트랜잭션이 적용된 프록시여야 한다.

  • 자동 프록시 생성기 적용 후 팩토리 빈이 더 이상 존재하지 않으므로 테스트용 클래스로 바꿔치기가 불가능
  • TestUserService 클래스를 직접 빈으로 등록

문제점

  1. TestUserService : 스태틱 내부 클래스
    • '$' 사용하여 등록
  2. 포인트컷 적용대상 : *ServiceImpl
    • 클래스명을 TestUserServiceImpl로 변경
  3. User 리스트에서 예외를 발생시킬 기준 id를 가져와 사용할 방법이 없음
    • 예외를 발생시킬 대상인 네 번째 사용자 아이디를 클래스에 넣자
public class UserServiceTest {
    static class TestUserServiceImpl extends UserServiceImpl {
        private String id = "madnite1";

        @Override
        protected void upgradeLvl(User user) {
            if (user.getId().equals(this.id)) {
                throw new TestUserServiceException();
            }
            super.upgradeLvl(user);
        }
    }
}

TestUserServiceImpl을 빈으로 등록

<!--
    스태틱 멤버 클래스는 $로 지정한다.
    프로퍼티 정의를 포함해서 userService 빈의 설정을 상속받는다.
-->
<bean id="testUserService" class="com.david.tobysspring.user.service.UserServiceTest$TestUserServiceImpl" parent="userService" />

특이사항

  1. $ 기호 : 스태틱 멤버 클래스를 지정할 때 사용
  2. parent : 다른 빈 설정을 상속받을 수 있음
    • 오버라이딩도 가능
    • class만 변경

upgradeAllOrNothing() 테스트

  • Autowired로 가져오면 됨
  • userService 빈과 타입이 중복되므로 빈 이름을 일치시켜줘야 함

코드는 단순해졌으나, 설정파일의 DI 정보까지 참고해야하므로 테스트 내용을 이해하기가 조금 어려워짐

자동생성 프록시 확인

테스트 코드 작성

  1. 트랜잭션이 필요한 빈에 트랜잭션 부가기능이 적용됐는가?
    • upgradeAllOrNothing() 테스트를 통해 검증 완료
  2. 아무 빈에나 트랜잭션 부가기능이 적용된 것은 아닌가?
    • 클래스 필터가 제대로 동작하여 프록시 생성 대상을 선별하고 있는지 여부

테스트 방법 1

  1. 포인트컷 빈의 클래스 이름 패턴을 변경하여 testUserService 빈에 트랜잭션이 적용되지 않도록 함
  2. 확인 후 원상복귀
<bean id="transactionPointCut" class="com.david.tobysspring.user.service.NameMatchClassMethodPointcut">
    <property name="mappedClassName" value="*NotServiceImpl" />
    <property name="mappedName" value="upgrade*" />
</bean>

테스트 방법 2

  • DefaultAdvisorAutoProxyCreator에 의해 userService빈이 프록시로 바꿔치기 됐다면 getBean("userService")로 가져온 오브젝트가 JDK의 Proxy 타입임을 확인
public class UserServiceTest {
    @Test
    public void advisorAutoProxyCreator() {
        assertThat(testUserService, instanceOf(java.lang.reflect.Proxy.class));
    }
}

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

좀 더 편리한 포인트컷 작성 방법

  • 필터나 매처에서 클래스와 메서드의 메타정보를 제공받으니 어떤 식이든 불가능할 것은 없음

포인트컷 표현식

AspectJExpressionPointcut 클래스를 사용

  • 클래스와 메소드의 선정 알고리즘을 한 번에 지정 가능
  • AspectJ 포인트컷 표현식이라고도 함

학습 테스트

  • 포인트컷의 선정 후보가 될 여러개의 메소드를 가진 클래스를 생성

TargetInterface.java

public interface TargetInterface {

    void hello();

    void hello(String a);

    int minus(int a, int b) throws RuntimeException;

    int plus(int a, int b);

}

Target.java

public class Target implements TargetInterface {
    @Override
    public void hello() {}
    @Override
    public void hello(String a) {}
    @Override
    public int minus(int a, int b) throws RuntimeException { return 0; }
    @Override
    public int plus(int a, int b) { return 0; }

    public void method() {}
}

Bean.java

public class Bean {
    public void method() throws RuntimeException {
    }
}

두 개의 클래스와 여섯 개의 메소드를 대상으로 포인트컷 표현식 적용

포인트컷 표현식 문법

AspectJ 포인트컷 표현식 : 포인트컷 지시자를 이용

  • 대표 : execution()
  • [] : 옵션
  • : or

execution([접근제한자 패턴] 타입패턴 [타입패턴.]이름패턴 (타입패턴 | "..", ... )[throws 예외 패턴])

메소드의 풀 시그니처를 문자열로 비교하는 개념

System.out.println(Target.class.getMethod("minus", int.class, int.class));
public int com.david.learningtest.spring.pointcut.Target.minus(int, int) throws java.lang.RuntimeException
  1. public
    • 접근제한자
    • public, private, protected 등
    • 생략 가능
    • 생략시 조건 미부여 (모든 접근제한자)
  2. int
    • 리턴 타입
    • 필수 항목
    • 와일드카드 : 모든 타입 선택
  3. david.learningtest.spring.pointcut.Target
    • 패키지와 타입 이름을 포함한 클래스의 타입 패턴
    • 생략 가능
    • 생략시 모든 타입 허용
    • 와일드가드 사용 가능
    • '..' : 한꺼번에 여러 개의 패키지 선택가능
  4. minus
    • 메소드 이름
    • 필수 항목
    • 와일드카드 : 모든 메소드 선택
  5. (int, int)
    • 메소드의 파라미터 타입
    • ','로 구분
    • '..' : 파라미터의 타입과 개수와 무관하게 모두 허용
    • '...' : 뒷부분의 파라미터 조건만 생략
  6. throws java.lang.RuntimeException
    • 예외 이름
    • 생략 가능

테스트 메소드

public class PointcutTest {
    @Test
    public void methodSignaturePointcut() throws SecurityException, NoSuchMethodException {
        AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
        pointcut.setExpression("execution(public int "
                + "com.david.learningtest.spring.pointcut.Target.minus(int,int) "
                + "throws java.lang.RuntimeException)");

        // Target.minus()
        assertThat(pointcut.getClassFilter().matches(Target.class) && 
                pointcut.getMethodMatcher().matches(
                        Target.class.getMethod("minus", int.class, int.class), null), is(true));

        // Target.plus()
        assertThat(pointcut.getClassFilter().matches(Target.class) && 
                pointcut.getMethodMatcher().matches(
                        Target.class.getMethod("plus", int.class, int.class), null), is(false));

        // Bean.method
        assertThat(pointcut.getClassFilter().matches(Bean.class) && 
                pointcut.getMethodMatcher().matches(
                        Target.class.getMethod("method"), null), is(false));
    }
}

포인트컷 표현식은 메소드 시그니처를 execution() 안에 넣어서 작성하낟.

포인트컷 표현식 테스트

필수 항목을 생략하면 간결하게 작성할 수 있다.

  • execution(int minus(int, int))
  • 단, 생략한 부분은 모든 경우를 다 허용하므로 정수값을 리턴하고 두 개의 정수형 파라미터를 갖는 minus라는 이름의 모든 메소드를 선정하는 느슨한 포인트컷이 된다.

모든 선정조건을 없애고 모든 메소드를 다 허용하는 포인트컷

  • execution(* *(..))

포인트컷과 메소드를 비교해주는 테스트 헬퍼 메소드

public class PointcutTest {
    public void pointcutMatches(String expression, Boolean expected, Class<?> clazz,
            String methodName, Class<?>... args) throws Exception {
        AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
        pointcut.setExpression(expression);

        assertThat(pointcut.getClassFilter().matches(clazz)
                && pointcut.getMethodMatcher().matches(clazz.getMethod(methodName, args), null), is(expected));
    }
}

타깃 클래스의 메소드 6개에 대해 포인트컷 선정 여부를 검사하는 헬퍼 메소드

public class PointcutTest {
    public void targetClassPointcutMatches(String expression, boolean... expected) throws Exception {
        pointcutMatches(expression, expected[0], Target.class, "hello");
        pointcutMatches(expression, expected[1], Target.class, "hello", String.class);
        pointcutMatches(expression, expected[2], Target.class, "plus", int.class, int.class);
        pointcutMatches(expression, expected[3], Target.class, "minus", int.class, int.class);
        pointcutMatches(expression, expected[4], Target.class, "method");
        pointcutMatches(expression, expected[5], Bean.class, "method");
    }
}

테스트 메소드

public class PointcutTest {
    @Test
    public void pointcut() throws Exception {
        targetClassPointcutMatches("execution(* *(..))", true, true, true, true, true, true);
        targetClassPointcutMatches("execution(* hello(..))", true, true, false, false, false, false);
        targetClassPointcutMatches("execution(* hello(String))", false, true, false, false, false, false);
        targetClassPointcutMatches("execution(* meth*(..))", false, false, false, false, true, true);
        targetClassPointcutMatches("execution(* *(int, int))", false, false, true, true, false, false);
        targetClassPointcutMatches("execution(* *())", true, false, false, false, true, true);
        targetClassPointcutMatches("execution(* com.david.learningtest.spring.pointcut.Target.*(..))",
                true, true, true, true, true, false);
        targetClassPointcutMatches("execution(* com.david.learningtest.spring.pointcut..*.*(..))",
                true, true, true, true, true, true);
        targetClassPointcutMatches("execution(* com.david.learningtest.spring.pointcut.*.*(..))",
                true, true, true, true, true, true);
        targetClassPointcutMatches("execution(* com.david..*.*(..))", true, true, true, true, true, true);
        targetClassPointcutMatches("execution(* springbook..*.*(..))", false, false, false, false, false, false);
        targetClassPointcutMatches("execution(* *..Target.*(..))", true, true, true, true, true, false);
        targetClassPointcutMatches("execution(* *..Tar*.*(..))", true, true, true, true, true, false);
        targetClassPointcutMatches("execution(* *..*get.*(..))", true, true, true, true, true, false);
        targetClassPointcutMatches("execution(* *..B*.*(..))", false, false, false, false, false, true);
        targetClassPointcutMatches("execution(* *..TargetInterface.*(..))", true, true, true, true, false, false);
        targetClassPointcutMatches("execution(* *(..) throws Runtime*)", false, false, false, true, false, true);
        targetClassPointcutMatches("execution(int *(..))", false, false, true, true, false, false);
        targetClassPointcutMatches("execution(void *(..))", true, true, false, false, true, true);
    }
}

결과




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

bean() : 빈의 이름으로 비교

  • bean(*Service) : 아이디가 Service로 끝나는 모든 빈을 선택

@annotation() : 특정 애노테이션 적용 여부로 비교

transactionPointcut 빈 제거 후 expression 프로퍼티에 execution() 추가

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

코드와 설정이 단순해지지만, 런타임시까지 문법의 검증이나 기능이 확인되지 않는다는 단점이 있음

타입 패턴과 클래스 이름 패턴

클래스 이름 패턴을 *..*ServiceImpl로 변경 후 TestUserService의 클래스 이름 역시 TestUserServiceImpl로 변경함

  • TestUserServiceImpl의 클래스명을 다시 TestUserService로 변경한다면?
  • 변경해도 테스트는 통과한다.

클래스 이름에 적용되는 패턴은 클래스 이름 패턴이 아니라 타입 패턴이다.

  • TeserUserService, UserSeriveImpl, UserService 세 가지 모두 적용 됨

6.5.4 AOP란 무엇인가

UserService에 트랜잭션을 적용해 온 과정

트랜잭션 서비스 추상화

트랜잭션 경계 설정 코드를 비즈니스 로직에 추가

  • 특정 트랜잭션 기술에 종속되는 코드가 되어 버림
  • 이를 해결하기 위해 서비스 추상화 기법을 적용

프록시와 데코레이터 패턴

여전히 비즈니스 로직 코드에 트랜잭션을 적용하고 있다는 사실이 드러남

  • DI를 이용해 데코레이터 패턴을 적용
  • 성격이 다른 코드로부터 자유로워지고, 독립적으로 로직을 검증하는 고립된 단위 테스트가 가능

다이내믹 프록시와 프록시 팩토리 빈

그러나 프록시 클래스는 만드는 작업이 번거로움

  • 프록시 클래스 없이 프록시 오브젝트를 런타임 시에 만들어주는 JDK 다이내믹 프록시 기술을 적용

자동 프록시 생성 방법과 포인트컷

트랜잭션 적용 빈마다 일일이 프록시 팩토리 빈을 설정해주는 것이 부담

  • 빈 생성 후처리기 기법을 활용하여 자동 프록시 생성기를 적용
  • 확장된 포인트컷을 사용

부가기능의 모듈화

소프트웨어 개발의 기본 : 관심사가 같은 코드를 분리해 한데 모으는 것

  • 트랜잭션 경계 설정 : 다른 모듈의 코드에 부가적으로 부여되는 기능
  • 한데 모을 수 없고 애플리케이션 전반에 여기저기 흩어져있음

TransactionAdvice라는 이름으로 모듈화시킴

  • 중복되지 않으며, 변경이 필요하면 한 곳만 수정하면 됨

AOP: 애스펙트 지향 프로그램

애스펙트 : 애플리케이션을 구성하는 중요한 한 가지 요소이고, 핵심기능에 부가되어 의미를 갖는 특별한 모듈

  • 어드바이스 : 부가될 기능을 정의한 코드
  • 포인트컷 : 어드바이스를 어디에 적용할 지를 결정

부가기능을 3차원의 다면체 구조로 가져가면서 성격이 다른 부가기능은 다른 면에 존재하도록

  • 핵심기능과 어우러져 동작할 수 있음
  • 설계와 개발은 독립적인 관점으로 작성 가능

6.5.5 AOP 적용 기술

프록시를 이용한 AOP

스프링 AOP의 핵심 : 프록시를 이용

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

AspectJ : 프록시를 사용하지 않는 대표적인 AOP

  • 컴파일된 타깃의 클래스 파일 자체를 수정하거나 클래스가 JVM에 로딩되는 시점을 가로채 바이트코드를 조작함

AspectJ의 장점

  1. DI 컨테이너의 도움을 받아 자동 프록시 생성 방식을 사용하지 않아도 AOP 적용 가능
  2. 강력하고 유연한 AOP가 가능

그러나 대부분의 부가기능은 프록시 방식을 사용해 메소드 호출시점에 부여하는 것으로 충분

6.5.6 AOP의 용어

  • 타깃 : 부가기능을 적용할 대상
  • 어드바이스 : 타깃에 제공할 부가기능을 담은 모듈
  • 조인포인트 : 어드바이스가 적용될 수 있는 위치, 스프링에서는 메소드의 실행 단계 뿐
  • 포인트컷 : 어드바이스를 적용할 조인 포인트를 선별하는 작업
  • 프록시 : 클라이언트와 타깃 사이에 투명하게 존재하면서 부가기능을 제공
  • 어드바이저 : AOP의 기본이 되는 모듈, 어드바이스 + 포인트컷, 스프링에서만 사용되는 용어
  • 애스펙트 : AOP의 기본 모듈

6.5.7 AOP 네임 스페이스

스프링 AOP를 적용하기 위해서는 최소 네가지 빈을 등록해야 한다.

  1. 자동 프록시 생성기 : DefaultAdvisorAutoProxyCreator
  2. 어드바이스 : 부가기능을 구현한 클래스
  3. 포인트컷 : AspectJExpressionPointcut
  4. 어드바이저 : DefaultPointcutAdvisor

AOP 네임 스페이스

스프링에서는 aop 스키마를 제공

<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/beans 
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">

    <aop:config>
        <aop:pointcut id="transactionPointCut" expression="execution(* *..*ServiceImpl.upgrade*(..))" />
        <aop:advisor advice-ref="transactionAdvice" pointcut-ref="transactionPointCut"/>
    </aop:config>
</beans>

, , 세 가지를 등록하면 그에 따라 세 개의 빈이 자동으로 등록됨

  • 직접 구현한 transactionAdvice를 제외한 AOP 빈은 독립된 전용 태그를 쓰는 것이 좋음

어드바이저 내장 포인트컷

포인트컷 표현식을 태그에 담아서 만드는 것도 가능

<aop:config>
    <aop:advisor advice-ref="transactionAdvice" pointcut="execution(* *..*ServiceImpl.upgrade*(..))"/>
</aop:config>

간결해서 보기는 좋으나, 하나의 포인트컷을 여러 개의 어드바이저에서 공유하는 경우에는 독립적으로 등록해서 사용해야 함


' Spring > 토비의 스프링 3.1' 카테고리의 다른 글

6.4 스프링의 프록시 팩토리 빈  (0) 2019.02.13
6.3 다이내믹 프록시와 팩토리 빈  (0) 2019.02.13
6.2 고립된 단위 테스트  (0) 2019.02.12
6.1 트랜잭션 코드의 분리  (0) 2019.02.07
6장. AOP  (0) 2019.02.07