작업의 목표
- 트랜잭션 코드를 깔끔하고 료과적으로 분리
- 트랜잭션 코드는 투명한 부가기능 형태로 제공
- 기존 설계와 코드에 영향을 주지 않음
6.5.1 자동 프록시 생성
대부분의 문제는 해결함
- 타깃 코드는 깔끔한 채로 남아있음
- 부가기능은 한 번만 만들어 모든 타깃과 메서드에 재사용 가능
- 타깃의 적용 메소드를 선정하는 방식도 독립적으로 작성 가능
남은 문제
- 부가기능이 타깃 오브젝트마다 새로 만들어짐
- ProxyFactoryBean의 어드바이스를 통해 해결
- 타깃 오브젝트마다 거의 비슷한 내용의 ProxyFactoryBean 설정정보를 추가해야함
중복 문제의 접근 방법
DAO
- 바뀌는 부분과 바뀌지 않는 부분을 구분해서 분리
- 템플릿과 콜백, 클라이어트를 분리
프록시 클래스 코드
- 다이내믹 프록시라는 런타임 코드 자동생성 기법을 이용하여 해결
- 변하지 않는 타깃으로의 위임과 부가기능 적용 여부는 다이내믹 프록시 기술에 맡김
- 변하는 부가 기능 코드는 별도로 만들어서 다이내믹 프록시 생성 팩토리에 DI로 제공
일정한 타깃 빈의 목록을 제공하면 자동으로 각 타깃 빈에 대한 프록시를 만들어준다면?
빈 후처리기를 이동한 자동 프록시 생성기
빈 후처리기
- 스프링 빈 오브젝트로 만들어 진 후 빈 오브젝트를 다시 가공
- BeanPostProcessor 인터페이스를 구현
DefaultAdvisorAutoProxyCreator
- 어드바이저를 이용한 자동 프록시 생성기
- 스프링에 적용하는 방법 : 빈 후처리기 자체를 빈으로 등록
- 빈 오브젝트가 생성될 때마다 빈 후처리기에 보내서 후처리 작업을 요청
워크플로우
- DefaultAdvisorAutoProxyCreator 빈 후처리기를 스프링 빈으로 등록
- 스프링이 빈 오브젝트를 생성할 때마다 후처리기에 빈을 보냄
- 포인트컷을 이용하여 전달받은 빈이 프록시 적용 대상인지를 확인
- 적용 대상일 경우 프록시 생성기에게 현재 빈에 대한 프록시를 만들게 하고, 만들어진 프록시에 어드바이저를 연결
확장된 포인트컷
포인트컷의 기능
- 클래스 필터
- 메소드 매처
public interface Pointcut {
// 프록시를 적용할 클래스인지 확인
ClassFilter getClassFilter();
// 어드바이스를 적용할 메소드인지 확인
MethodMatcher getMethodMatcher();
}
지금까지는 메소드 매처 기능만 사용
- NameMatchMethodPointcut은 클래스 필터 기능이 없음
포인트컷 테스트
포인트컷 학습 테스트
- NameMatchMethodPointcut을 확장하여 클래스 필터를 구현
- 프록시 적용 후보 클래스를 여러 개 만듦
- 포인트컷을 적용한 ProxyFactoryBean으로 프록시를 만들도록 함
- 어드바이스 적용 여부 확인
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(포인트컷)
- NameMatchMethodPointcut을 내부 익명 클래스 방식으로 확장해서 만듦
- ClassFilter()를 오버라이딩해서 HelloT로 시작하는 클래스만 선정
- 메소드 이름 선정기준은 기존 것을 그대로 유지
- 테스트 대상 (세가지)
- HelloTarget을 상속(내용은 같음)
- 클래스 이름이 다름
- 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 클래스를 직접 빈으로 등록
문제점
- TestUserService : 스태틱 내부 클래스
- '$' 사용하여 등록
- 포인트컷 적용대상 : *ServiceImpl
- 클래스명을 TestUserServiceImpl로 변경
- 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" />
특이사항
- $ 기호 : 스태틱 멤버 클래스를 지정할 때 사용
- parent : 다른 빈 설정을 상속받을 수 있음
- 오버라이딩도 가능
- class만 변경
upgradeAllOrNothing() 테스트
- Autowired로 가져오면 됨
- userService 빈과 타입이 중복되므로 빈 이름을 일치시켜줘야 함
코드는 단순해졌으나, 설정파일의 DI 정보까지 참고해야하므로 테스트 내용을 이해하기가 조금 어려워짐
자동생성 프록시 확인
테스트 코드 작성
- 트랜잭션이 필요한 빈에 트랜잭션 부가기능이 적용됐는가?
- upgradeAllOrNothing() 테스트를 통해 검증 완료
- 아무 빈에나 트랜잭션 부가기능이 적용된 것은 아닌가?
- 클래스 필터가 제대로 동작하여 프록시 생성 대상을 선별하고 있는지 여부
테스트 방법 1
- 포인트컷 빈의 클래스 이름 패턴을 변경하여 testUserService 빈에 트랜잭션이 적용되지 않도록 함
- 확인 후 원상복귀
<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
- public
- 접근제한자
- public, private, protected 등
- 생략 가능
- 생략시 조건 미부여 (모든 접근제한자)
- int
- 리턴 타입
- 필수 항목
- 와일드카드 : 모든 타입 선택
- david.learningtest.spring.pointcut.Target
- 패키지와 타입 이름을 포함한 클래스의 타입 패턴
- 생략 가능
- 생략시 모든 타입 허용
- 와일드가드 사용 가능
- '..' : 한꺼번에 여러 개의 패키지 선택가능
- minus
- 메소드 이름
- 필수 항목
- 와일드카드 : 모든 메소드 선택
- (int, int)
- 메소드의 파라미터 타입
- ','로 구분
- '..' : 파라미터의 타입과 개수와 무관하게 모두 허용
- '...' : 뒷부분의 파라미터 조건만 생략
- 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의 장점
- DI 컨테이너의 도움을 받아 자동 프록시 생성 방식을 사용하지 않아도 AOP 적용 가능
- 강력하고 유연한 AOP가 가능
그러나 대부분의 부가기능은 프록시 방식을 사용해 메소드 호출시점에 부여하는 것으로 충분
6.5.6 AOP의 용어
- 타깃 : 부가기능을 적용할 대상
- 어드바이스 : 타깃에 제공할 부가기능을 담은 모듈
- 조인포인트 : 어드바이스가 적용될 수 있는 위치, 스프링에서는 메소드의 실행 단계 뿐
- 포인트컷 : 어드바이스를 적용할 조인 포인트를 선별하는 작업
- 프록시 : 클라이언트와 타깃 사이에 투명하게 존재하면서 부가기능을 제공
- 어드바이저 : AOP의 기본이 되는 모듈, 어드바이스 + 포인트컷, 스프링에서만 사용되는 용어
- 애스펙트 : AOP의 기본 모듈
6.5.7 AOP 네임 스페이스
스프링 AOP를 적용하기 위해서는 최소 네가지 빈을 등록해야 한다.
- 자동 프록시 생성기 : DefaultAdvisorAutoProxyCreator
- 어드바이스 : 부가기능을 구현한 클래스
- 포인트컷 : AspectJExpressionPointcut
- 어드바이저 : 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 |