Spring AOP
비즈니스 로직에서 반복적으로 사용되는 트랜잭션 코드를 효과적으로 분리하여 부가 기능으로 제공하는 것입니다. 이로써 분리된 트랜잭션 코드는 마치 투명한 유리처럼 다른 코드에 영향을 주지 않으면서도 메소드 호출 과정에서 동적으로 참여하여 부가 기능을 제공합니다. 이 투명성은 언제든지 부가 기능을 추가하거나 제거할 수 있으며, 기존 코드는 항상 원래 상태를 유지할 수 있습니다. 이것은 의존성 주입(DI)의 멋진 응용 방식 중 하나로, 코드를 깔끔하게 유지하면서도 유연성을 제공합니다.
자동 프록시 생성
스프링 프레임워크에서 AOP를 구현할 때, ProxyFactoryBean을 사용하는 접근 방식은 많은 장점을 가지지만, 설정의 중복 문제가 종종 발생합니다. 이는 특히, 대규모 애플리케이션에서 빈 설정의 관리를 어렵게 만드는 요소입니다. 다행히도, 스프링은 이러한 중복을 줄이고 더욱 효율적으로 AOP를 적용할 수 있는 방법을 제공합니다.
해결 방안: 중복 최소화를 위한 전략
1. Abstract Factory 패턴 사용: 공통의 설정을 가진 ProxyFactoryBean을 생성하는 팩토리 클래스를 구현하여, 공통된 설정을 재사용할 수 있습니다. 이 팩토리 클래스는 타깃 객체만 변경하여 새로운 프록시 빈을 생성하는 역할을 합니다.
2. Bean Definition 상속: 스프링의 빈 설정에서는 하나의 빈 정의를 다른 빈 정의의 부모로 설정하여, 공통의 속성을 상속받을 수 있습니다. target 프로퍼티를 제외한 나머지 설정(빈 클래스, 어드바이스, 포인트컷 등)을 부모 빈에 정의하고, 각 타깃 별 빈에서는 target 속성만을 오버라이드합니다.
3. 어노테이션 기반 AOP 사용: @Aspect 어노테이션을 사용하는 방식으로 전환하면 XML 기반의 복잡한 설정을 줄일 수 있습니다. 어노테이션 기반 접근은 코드 내에서 직접 어드바이스와 포인트컷을 정의하므로, 별도의 빈 설정이 필요 없어집니다.
4. 자동 프록시 생성기 사용: AspectJAutoProxyCreator나 DefaultAdvisorAutoProxyCreator와 같은 자동 프록시 생성기를 사용하면, 각 타깃 별로 별도의 프록시 설정을 정의할 필요가 없습니다. 이들은 어드바이저를 자동으로 탐지하고, 적절한 타깃에 프록시를 적용합니다.
5. Java Config 사용: XML 설정 대신 Java Config를 사용하면, 코드 내에서 프록시 빈을 더 유연하고 동적으로 구성할 수 있습니다. 이 방법은 설정의 중복을 줄이고, 프로그래밍 방식으로 더 세밀한 제어를 가능하게 합니다.
ProxyFactoryBean을 사용할 때 발생하는 설정의 중복 문제는 위와 같은 방법들을 통해 해결할 수 있습니다. 이러한 접근은 단순히 설정의 중복을 줄이는 것을 넘어, AOP를
더 효율적이고 유연하게 적용하는 데에 기여합니다. 코드의 중복을 최소화하고, 관리의 편의성을 높이며, AOP 구현의 복잡성을 줄이는 것은 대규모 시스템에서 특히 중요합니다.
최적화된 AOP 구현을 위한 추가 고려 사항
1. 유지보수 용이성: 구현 방법을 선택할 때, 미래의 변경 사항을 쉽게 적용할 수 있는 방식을 고려해야 합니다. 유지보수가 용이한 설정은 시스템의 장기적 안정성과 확장성에 기여합니다.
2. 성능 고려: AOP의 적용은 성능에 영향을 미칠 수 있습니다. 특히, 고성능을 요구하는 애플리케이션에서는 프록시의 오버헤드를 최소화하는 방식을 선택해야 합니다.
3. 컨텍스트 이해: AOP 적용 시 전체 애플리케이션의 컨텍스트를 이해하는 것이 중요합니다. 어떤 타입의 프록시를 사용할지, 어느 수준까지 포인트컷을 세분화할지 등은 애플리케이션의 전체적인 구조와 요구 사항을 고려하여 결정해야 합니다.
4. 문서화와 교육: AOP 구현은 종종 복잡할 수 있으므로, 관련 문서화와 팀 내 교육은 이해도를 높이고, 장기적으로 효과적인 사용을 보장하는 데 중요합니다.
AOP의 구현은 단순히 기술적인 측면을 넘어서, 설계와 관리의 관점에서도 접근해야 합니다. 중복을 줄이고 효율성을 높이는 것은 기술적인 우수성뿐만 아니라, 시스템의 유지보수성과 확장성을 위해서도 필수적입니다.
중복 문제의 접근 방법
여러분이 경험한 문제는 소프트웨어 개발에서 흔히 마주치는 패턴인 반복적이고 기계적인 작업의 자동화에 관한 것입니다. 이를 해결하기 위해 다양한 기술적 접근 방식이 사용되었습니다.
JDBC API와 DAO 코드의 최적화
- 문제: JDBC API를 사용하는 DAO(Data Access Object) 코드에서는 try/catch/finally 블록이 반복적으로 사용되어 코드의 중복이 발생했습니다.
- 해결 방법: 이 문제는 템플릿과 콜백, 그리고 클라이언트 분리를 통해 해결되었습니다. 전략 패턴과 의존성 주입(Dependency Injection, DI)을 적용하여, 변경되는 부분과 변경되지 않는 부분을 분리했습니다. 이를 통해 코드의 재사용성을 높이고, 유지보수를 용이하게 만들었습니다.
프록시 클래스 코드의 최적화
- 문제: 타깃 오브젝트로의 위임과 부가 기능 적용을 위한 코드가 모든 타깃 인터페이스 메소드에 반복적으로 필요했습니다.
- 해결 방법: 이 문제는 다이내믹 프록시라는 런타임 코드 자동생성 기법을 이용하여 해결했습니다. JDK의 다이내믹 프록시는 런타임 시 특정 인터페이스를 구현한 타깃 오브젝트의 프록시 역할을 하는 클래스를 내부적으로 생성합니다. 이 접근 방식은 개발자가 수동으로 모든 타깃 인터페이스 메소드를 구현하는 프록시 클래스를 만들 필요를 없애주었습니다.
ProxyFactoryBean 설정의 자동화
- 현재 문제: 각 타깃 오브젝트마다 거의 비슷한 내용의 ProxyFactoryBean 빈 설정을 추가하는 것은 반복적이고 기계적인 작업입니다.
- 가능한 해결책: ProxyFactoryBean 설정의 자동등록 기법을 고려해볼 수 있습니다. 예를 들어, 특정 타깃 빈들의 목록을 제공하면, 이를 기반으로 자동으로 각 타깃 빈에 대한 프록시를 생성하는 방법이 될 수 있습니다. 이는 다이내믹 프록시가 인터페이스만 제공되면 모든 메소드에 대한 구현 클래스를 자동으로 만드는 것과 유사한 개념입니다.
반복적이고 기계적인 작업의 자동화는 소프트웨어 개발에서 효율성과 정확성을 높이는 중요한 요소입니다. JDBC API와 DAO 코드, 그리고 프록시 클래스 코드의 경우와 같이, 기술적인 해결 방법들이 이미 성공적으로 적용되었습니다. 이제 ProxyFactoryBean 설정의 자동화를 통해 이러한 접근 방식을 확장하고, 반복적인 설정 작업을 최소화하는 것이 다음 단계가 될 수 있습니다. 이러한 자동화는 개발 프로세스의 효율성을 크게 향상시킬 뿐만 아니라, 실수의 가능성을 줄이고, 전체적인 코드 품질을 높이는 데에도 기여합니다.
추가 고려사항
- 자동화의 한계 인식: 자동화는 많은 장점을 가지고 있지만, 모든 상황에 적합한 것은 아닙니다. 특정한 경우에는 수동 설정이 더 유연하거나 효과적일 수 있으므로, 자동화의 적용 범위와 한계를 잘 이해하는 것이 중요합니다.
- 커스터마이제이션 고려: 자동화된 프로세스에서도 특정 경우에 대한 커스터마이제이션을 허용하는 유연성이 필요할 수 있습니다. 이를 통해 예외적인 상황이나 특별한 요구사항을 효과적으로 처리할 수 있습니다.
- 유지보수와 문서화: 자동화된 시스템은 종종 이해하기 어려울 수 있으므로, 충분한 문서화와 쉬운 유지보수를 위한 설계가 중요합니다. 이는 장기적으로 시스템의 안정성과 확장성을 보장하는 데 도움이 됩니다.
결국, 이러한 자동화는 개발 프로세스를 단순화하고 효율성을 높이는 방법입니다. 그러나 이를 적용함에 있어서는 전체적인 시스템 아키텍처, 유지보수의 용이성, 그리고 팀원들의 이해도와 같은 요소들을 고려해야 합니다. 잘 설계된 자동화는 개발자의 노력을 크게 절감시키고, 더 집중적이고 창의적인 작업에 더 많은 시간을 할애할 수 있게 해줍니다.
BeanPostProcessor를 이용한 자동 프록시 생성기
DefaultAdvisorAutoProxyCreator는 스프링 프레임워크에서 제공하는 BeanPostProcessor 중 하나로, Aspect-Oriented Programming (AOP)를 구현하는 데 사용됩니다. BeanPostProcessor는 스프링 빈의 초기화 단계에서 특정 작업을 수행하도록 확장할 수 있는 강력한 메커니즘을 제공합니다.
BeanPostProcessor란?
- 목적: 스프링 컨테이너가 빈의 인스턴스화와 의존성 주입을 마친 후, 초기화 단계에서 추가적인 처리를 하도록 합니다.
- 활용: 빈의 프로퍼티 설정 후, 초기화 콜백(init-method, @PostConstruct 등)이 호출되기 전과 후에 사용자 정의 처리를 할 수 있습니다.
- 적용: 이 인터페이스를 구현한 클래스를 스프링 컨테이너에 빈으로 등록하면, 컨테이너는 모든 빈의 초기화 단계에서 해당 BeanPostProcessor의 메소드를 호출합니다.
DefaultAdvisorAutoProxyCreator의 역할
DefaultAdvisorAutoProxyCreator는 BeanPostProcessor를 구현한 클래스로, AOP 프록시를 자동으로 생성하는 역할을 합니다.
- 프록시 생성: 스프링 빈이 생성될 때, 등록된 어드바이저(Advisor)와 매칭되는 경우에 대해 자동으로 프록시를 생성합니다.
- AOP 적용: 생성된 프록시를 통해 AOP 어드바이스(Advice)가 해당 빈의 메소드 호출에 적용됩니다. 이를 통해 트랜잭션 관리, 보안, 로깅 등의 부가 기능을 빈에 적용할 수 있습니다.
- 유연성: DefaultAdvisorAutoProxyCreator는 매우 유연하게 AOP를 적용할 수 있게 해주며, 개발자는 코드 변경 없이도 다양한 부가 기능을 적용하거나 변경할 수 있습니다.
사용 방법
1. 어드바이저 정의: 특정 기능을 수행하는 Advice와 이를 적용할 Pointcut을 정의합니다.
2. 빈 등록: DefaultAdvisorAutoProxyCreator를 스프링 컨테이너에 빈으로 등록합니다.
3. 자동 프록시 생성: 스프링 컨테이너는 빈을 생성할 때 DefaultAdvisorAutoProxyCreator를 사용하여 조건에 맞는 빈에 대해 자동으로 프록시를 생성하고, 정의된 어드바이스를 적용합니다.
활용 예시
- 트랜잭션 관리: 트랜잭션 경계를 설정하는 어드바이스를 통해 메소드 호출 전후에 트랜잭션을 시작하고 커밋 또는 롤백하는 로직을 적용할 수 있습니다.
- 보안 검사: 메소드 실행 전에 사용자의 권한을 검사하는 보안 로직을 어드바이스로 적용합니다.
- 로깅과 모니터링: 실행 시간을 측정하거나 메소드 호출 정보를 로깅하는 기능을 어드바이스로 구현합니다.
장점
- 중복 감소: 비즈니스 로직과 부가 기능을 분리함으로써 코드의 중복을 줄일 수 있습니다.
- 유연성 증가: 부가 기능의 변경이나 추가가 필요할 때, 어드바이스나 포인트컷만 수정하면 되므로, 비즈니스 로직에 미치는 영향을 최소화할 수 있습니다.
- 유지보수 용이: 프록시를 통한 AOP 적용은 유지보수를 용이하게 만듭니다. 코드의 변경 없이 다양한 부가 기능을 적용하거나 변경할 수 있습니다.
주의사항
- 성능 고려: 프록시를 통한 메소드 호출은 약간의 성능 오버헤드를 발생시킬 수 있으므로, 성능에 민감한 영역에서는 신중히 적용해야 합니다.
- 복잡성 관리: 다수의 어드바이저와 복잡한 포인트컷 표현식은 시스템의 복잡성을 증가시킬 수 있으므로, 명확하고 간단하게 유지하는 것이 중요합니다.
- 테스트와 디버깅: 프록시 기반의 AOP는 디버깅과 테스트를 어렵게 만들 수 있으므로, 이에 대한 충분한 고려가 필요합니다.
DefaultAdvisorAutoProxyCreator와 같은 BeanPostProcessor의 활용은 스프링 애플리케이션의 유연성과 확장성을 대폭 향상시킬 수 있습니다. 스프링의 이러한 기능을 잘 활용하면, 애플리케이션의 아키텍처를 효율적으로 설계하고 유지보수할 수 있게 됩니다.
확장된 포인트컷
스프링 프레임워크에서 포인트컷(Pointcut)의 개념은 AOP(Aspect-Oriented Programming) 구현에 있어서 중요한 부분입니다. 일반적으로 포인트컷은 타깃 객체의 어떤 메소드에 부가기능(Advice)을 적용할지 결정하는 역할을 합니다. 하지만, 포인트컷의 역할은 이보다 더 확장될 수 있습니다.
포인트컷의 확장된 개념
포인트컷은 두 가지 주요 기능을 가지고 있습니다:
1. 클래스 필터(ClassFilter): 이는 프록시를 적용할 클래스를 선별합니다. 즉, 어떤 클래스가 AOP의 대상이 될 수 있는지 결정합니다.
2. 메소드 매처(MethodMatcher): 이는 클래스 내에서 어드바이스를 적용할 특정 메소드를 선정합니다.
포인트컷의 이중 역할
- 메소드 선별에 집중: 초기에는 포인트컷의 메소드 선별 기능만을 주로 사용했습니다. 예를 들어, NameMatchMethodPointcut은 메소드 이름을 기준으로 어드바이스 적용 여부를 결정합니다. 이 경우, 클래스 필터는 모든 클래스를 수용하도록 설정됩니다.
- 클래스와 메소드 둘 다 고려: ProxyFactoryBean과 같은 경우, 타깃이 이미 정해져 있으므로 주로 메소드 매처만 사용됩니다. 하지만 DefaultAdvisorAutoProxyCreator 같은 빈 후처리기를 사용할 때는, 클래스 필터와 메소드 매처 둘 다 고려됩니다. 이 경우, 먼저 프록시를 적용할 클래스를 판단한 후, 해당 클래스의 메소드에 대해 어드바이스 적용 여부를 결정합니다.
포인트컷은 AOP 구현에서 두 가지 중요한 역할을 수행합니다. 클래스 필터는 전체 클래스 중에서 AOP의 대상이 될 클래스를 선별하는 반면, 메소드 매처는 해당 클래스 내의 특정 메소드에 AOP를 적용할지 결정합니다. 이러한 이중 기능은 스프링 AOP가 더욱 유연하고 강력하게 작동할 수 있게 해주며, 다양한 시나리오에 적합한 AOP 구성을 가능하게 합니다. 따라서 포인트컷은 단순히 메소드 레벨의 선정뿐만 아니라 클래스 레벨에서도 중요한 역할을 하며, 이를 통해 보다 세밀하고 효과적인 AOP 구현이 가능해집니다.
리스트 6-49 두 가지 기능을 정의한 Pointcut 인터페이스
public interface Pointcut {
ClassFilter getClassFilter(); --> 프록시를 적용할 클래스인지 확인해 준다.
MethodMatcher getMethodMatcher(); --> 어드바이스를 적용할 메소드인지 확인해 준다.
}
스프링 프레임워크의 ProxyFactoryBean과 DefaultAdvisorAutoProxyCreator는 Aspect-Oriented Programming (AOP)을 구현하는 데 사용되는 두 가지 중요한 컴포넌트입니다. 이들은 각기 다른 방식으로 프록시를 생성하고 관리하며, 이에 따라 포인트컷의 사용 방식에도 차이가 있습니다.
ProxyFactoryBean
- 목적: ProxyFactoryBean은 주로 특정 빈에 대해 명시적으로 프록시를 생성하는 데 사용됩니다. 여기서는 개발자가 직접 어떤 빈에 프록시를 적용할지 결정합니다.
- 클래스 필터의 필요성: ProxyFactoryBean에서는 클래스 레벨의 필터링이 크게 중요하지 않습니다. 왜냐하면, 프록시를 적용할 대상이 미리 정해져 있기 때문입니다. 따라서 주로 메소드 레벨에서의 선별 작업(어떤 메소드에 어드바이스를 적용할지 결정하는 것)에 중점을 둡니다.
DefaultAdvisorAutoProxyCreator
- 클래스 필터의 필요성: ProxyFactoryBean에서는 클래스 레벨의 필터링이 크게 중요하지 않습니다. 왜냐하면, 프록시를 적용할 대상이 미리 정해져 있기 때문입니다. 따라서 주로 메소드 레벨에서의 선별 작업(어떤 메소드에 어드바이스를 적용할지 결정하는 것)에 중점을 둡니다.
- 목적: DefaultAdvisorAutoProxyCreator는 빈후처리기(BeanPostProcessor)로 작동하며, 스프링 컨테이너에 등록된 모든 빈을 대상으로 자동으로 프록시를 생성할 수 있습니다.
- 클래스와 메소드 선정의 중요성: 이 컴포넌트에서는 포인트컷이 클래스 필터(ClassFilter)와 메소드 매처(MethodMatcher) 두 가지 기능을 모두 사용합니다. 여기서 클래스 필터는 어떤 빈이 프록시의 대상이 될지 결정하며, 메소드 매처는 해당 빈의 어떤 메소드에 어드바이스를 적용할지 결정합니다.
어드바이저의 역할
- 어드바이저: 포인트컷과 어드바이스를 결합한 것으로, 어떤 경우에 어떤 어드바이스(부가기능)를 적용할지 정의합니다.
- DefaultAdvisorAutoProxyCreator에서의 사용: DefaultAdvisorAutoProxyCreator는 등록된 어드바이저를 기반으로 자동 프록시 생성을 수행합니다. 각 어드바이저는 포인트컷을 통해 프록시 적용 대상과 메소드를 결정하고, 해당 메소드에 적용할 어드바이스를 정의합니다.
- ProxyFactoryBean과 DefaultAdvisorAutoProxyCreator는 스프링 AOP 구현에서 각각의 역할과 중요성을 가지며, 그들의 사용 방식과 포인트컷의 적용 방식에 차이가 있습니다.
- ProxyFactoryBean은 주로 특정 빈에 대한 프록시를 명시적으로 생성할 때 사용됩니다. 이 경우, 클래스 레벨의 필터링보다는 메소드 레벨에서의 선별이 중요합니다.
- DefaultAdvisorAutoProxyCreator는 스프링 컨테이너에 등록된 모든 빈에 대해 자동으로 프록시를 생성하고 관리하는 데 사용됩니다. 이 컴포넌트에서는 클래스 필터와 메소드 매처를 모두 활용하는 포인트컷이 필요합니다. 클래스 필터는 어떤 빈이 프록시 대상이 될지 결정하고, 메소드 매처는 해당 빈의 어떤 메소드에 어드바이스를 적용할지 결정합니다.
이러한 차이는 스프링 AOP의 유연성을 보여줍니다. 개발자는 애플리케이션의 요구사항과 구조에 따라 적절한 방식을 선택하여 AOP를 구현할 수 있습니다. 어드바이저를 사용하여 포인트컷과 어드바이스를 결합함으로써, 스프링은 AOP를 통한 부가기능의 적용을 보다 쉽고 효율적으로 만들어 줍니다. 이를 통해, 개발자는 비즈니스 로직과 부가기능의 관심사를 명확하게 분리하고, 코드의 재사용성과 유지보수성을 높일 수 있습니다.
DefaultAdvisorAutoProxyCreator의 적용
프록시 자동생성 방식에서 사용할 포인트컷을 적용.
클래스 필터를 적용한 포인트컷 작성
이 내용은 NameMatchMethodPointcut을 확장하여 클래스 필터 기능을 추가하는 작업에 대한 설명입니다. NameMatchMethodPointcut은 원래 메소드 이름을 기준으로 어드바이스 적용 여부를 판단하는 포인트컷입니다. 이번 작업의 목적은 이 포인트컷에 클래스 필터 기능을 추가하여, 특정 클래스 이름 패턴을 가진 클래스에 대해서만 어드바이스를 적용할 수 있도록 확장하는 것입니다.
클래스 필터를 적용한 포인트컷의 개발
- 개발 목적: 기존의 NameMatchMethodPointcut에 클래스 이름 기반 필터링 기능을 추가함으로써, 더 세밀한 AOP 적용이 가능하도록 합니다.
- 작업 내용: NameMatchMethodPointcut을 상속받는 새로운 클래스를 생성하고, 여기에 클래스 이름을 비교하는 ClassFilter 기능을 추가합니다. 이렇게 하면, 특정 클래스에 대해서만 어드바이스를 적용할 수 있게 됩니다.
- ClassFilter 추가: 클래스 필터는 주어진 클래스 이름 패턴과 일치하는 클래스에 대해서만 어드바이스를 적용하도록 구현됩니다. 이를 통해 메소드 매칭뿐만 아니라 클래스 레벨에서도 어드바이스 적용 여부를 제어할 수 있습니다.
- 활용: 이 확장된 포인트컷을 사용하면, 특정 패턴을 가진 클래스 이름에 대해서만 특정 메소드에 어드바이스를 적용할 수 있습니다. 이는 AOP를 더 유연하고 효과적으로 적용하는 데 도움이 됩니다.
클래스 필터를 추가한 아래 포인트컷은 스프링 AOP에서의 활용도를 높입니다. 클래스 레벨에서의 필터링 기능을 통해 개발자는 더욱 정교한 AOP 전략을 구현할 수 있게 되며, 이는 애플리케이션의 특정 부분에만 부가기능을 적용하는 등의 세밀한 조정이 필요할 때 매우 유용합니다. 이러한 확장은 스프링 AOP의 유연성을 잘 보여주는 예시로, 복잡한 비즈니스 요구사항을 충족시키는 데 필수적인 기능입니다.
리스트 6-51 클래스 필터가 포함된 포인트
import org.springframework.aop.ClassFilter;
import org.springframework.aop.support.NameMatchMethodPointcut;
import org.springframework.util.PatternMatchUtils;
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) {
return PatternMatchUtils.simpleMatch(mappedName, clazz.getSimpleName());
}
}
}
어드바이저를 이용하는 자동 프록시 생성기 등록
이 내용은 DefaultAdvisorAutoProxyCreator를 사용하여 자동 프록시 생성기를 등록하는 과정을 설명하고 있습니다. DefaultAdvisorAutoProxyCreator는 스프링의 Aspect-Oriented Programming (AOP) 기능 중 하나로, 스프링 컨테이너에 등록된 모든 빈에 대해 자동으로 프록시를 생성하고 관리하는 역할을 합니다.
DefaultAdvisorAutoProxyCreator의 역할
- Advisor 탐색: DefaultAdvisorAutoProxyCreator는 스프링 컨테이너에 등록된 모든 Advisor 인터페이스 구현체를 찾습니다.
- 프록시 대상 선정: 이 컴포넌트는 생성되는 모든 빈에 대해서 어드바이저의 포인트컷을 적용하여 프록시 적용 대상을 선별합니다.
- 프록시 생성 및 교체: 선정된 빈 클래스에 대해서는 프록시를 생성하고, 원래의 빈 오브젝트를 이 프록시 오브젝트로 대체합니다. 이로 인해, 해당 빈에 의존하는 다른 컴포넌트들은 이제 프록시 오브젝트를 사용하게 됩니다.
DefaultAdvisorAutoProxyCreator 등록의 간결함
- 간단한 등록 과정: DefaultAdvisorAutoProxyCreator의 등록은 매우 간단합니다. 스프링 설정 파일에 단 한 줄을 추가하는 것만으로도 충분하며, 이로써 스프링 애플리케이션은 자동 프록시 생성의 이점을 누릴 수 있습니다.
DefaultAdvisorAutoProxyCreator를 사용하는 것은 스프링 AOP를 보다 간편하고 효율적으로 적용할 수 있는 방법입니다. 이를 통해 개발자는 복잡한 프록시 설정 과정 없이도, AOP를 활용하여 애플리케이션의 여러 부분에 효과적으로 부가기능을 적용할 수 있습니다. 이는 코드의 중복을 줄이고, 유지보수를 용이하게 하며, 애플리케이션의 전체적인 설계를 개선하는 데 기여합니다.
@Bean
public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {
return new DefaultAdvisorAutoProxyCreator();
}
Spring Bean Name
다음과 같은 Configuration 메타데이터가 있고,
@Configuration
public class TestServiceFactory {
// ...
@Bean
public UserServiceImpl userService() {
UserServiceImpl userServiceImpl = new UserServiceImpl();
userServiceImpl.setUserDao(userDao());
userServiceImpl.setMailSender(mailSender());
return userServiceImpl;
}
@Bean
public UserServiceImpl userServiceImpl() {
UserServiceImpl userServiceImpl = new UserServiceImpl();
userServiceImpl.setUserDao(userDao());
userServiceImpl.setMailSender(mailSender());
return userServiceImpl;
}
}
다음과 단위테스트에서 사용될 픽스처 변수가 있습니다.
각각의 픽스처 변수에 주입될 스프링 빈 객체는 스프링 빈 이름으로 결정됩니다.
즉, 첫번째 필드는 userService 이름의 스프링 빈이 주입되고,
두번째 필드는 userServiceImpl 이름의 스프링 빈이 주입됩니다.
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = {TestServiceFactory.class})
public class UserServiceTest {
// 생략...
@Autowired UserService userService;
@Autowired UserService userServiceImpl;
// 생략...
}
다음 이미지들은 디버그 모드에서 userService 빈과 단위 테스트의 테스트 픽스처인 userService 필드의 내역입니다.
그림 1. userService 스프링 빈
그림 2. userService 필드의 해쉬 코드1
그림 3. userService 필드의 해쉬 코드2
그림 3. 이미지에서 userService 필드는 JdkDynamicAopProxy의 인스턴스를 참조하고 있으며, 이 프록시 객체의 해시코드는 @91721432입니다. 그리고 이 프록시 객체의 타깃은 userService 이름의 스프링 빈입니다.
위 코드에서 제공된 두 @Bean 팩토리 메소드(userService와 userServiceImpl)는 UserServiceImpl 인스턴스를 생성하고 빈으로 등록합니다. 이 두 메소드는 동일한 타입(UserServiceImpl)의 빈을 반환하지만, 스프링은 메소드 이름을 기반으로 빈의 이름을 결정합니다. 따라서, 첫 번째 빈의 이름은 userService, 두 번째 빈의 이름은 userServiceImpl입니다.
단위 테스트 클래스의 @Autowired를 사용하여 필드에 주입하는 부분을 살펴보겠습니다:
1. @Autowired UserService userService;
이 필드에는 UserService 타입의 빈이 주입됩니다. 여기서 UserService는 인터페이스이며, 스프링은 이 인터페이스를 구현하는 적절한 빈을 찾아야 합니다. 스프링은 userService라는 이름의 빈(userServiceImpl 팩토리 메소드에서 생성된 빈)을 찾고, 이를 userService 필드에 주입합니다. 이 주입은 빈의 이름이 필드 이름과 일치하기 때문에 이루어집니다.
2. @Autowired UserService userServiceImpl;
이 필드에도 UserService 타입의 빈이 주입됩니다. 필드의 이름은 userServiceImpl이므로, 스프링은 userServiceImpl라는 이름의 빈(userServiceImpl 메소드에서 생성된 빈)을 찾아 이 필드에 주입합니다.
결론적으로, 두 필드 모두 UserServiceImpl 타입의 빈을 주입받지만, 스프링은 빈의 이름을 기반으로 어떤 빈을 주입할지 결정합니다. 이 경우 빈의 이름은 각각의 @Bean 팩토리 메소드의 이름에 의해 결정됩니다. 이러한 방식으로, 스프링은 다양한 설정과 상황에서 유연하게 의존성을 주입할 수 있습니다.
@Qualifier
@Qualifier 어노테이션은 특정한 종류의 스프링 빈(bean) 중에서 하나를 선택하여 의존성 주입(Dependency Injection)을 할 때 사용됩니다. Spring에서는 같은 타입의 빈이 여러 개 있을 때, 어떤 빈을 주입해야 할지 명확히 지정하기 위해 @Qualifier를 사용합니다.
예를 들어, 같은 인터페이스를 구현하는 여러 클래스가 있고, 이들 각각을 빈으로 등록했다고 가정해보겠습니다. 이 경우, Spring이 어떤 구현체를 주입해야 할지 알 수 없어서 오류가 발생할 수 있습니다. @Qualifier 어노테이션을 사용하면, 개발자는 특정 빈의 이름을 지정하여 명확하게 어떤 빈을 사용할지 결정할 수 있습니다.
package com.coga.springframe.service;
import javax.sql.DataSource;
import org.springframework.aop.framework.ProxyFactoryBean;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.aop.support.NameMatchMethodPointcut;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.jdbc.datasource.SimpleDriverDataSource;
import com.coga.springframe.dao.UserDaoJdbc;
@Configuration
public class TestServiceFactory {
// 생략 ...
@Bean
public UserServiceImpl userService() {
UserServiceImpl userServiceImpl = new UserServiceImpl();
userServiceImpl.setUserDao(userDao());
userServiceImpl.setMailSender(mailSender());
return userServiceImpl;
}
@Bean
public UserServiceImpl userServiceImpl() {
UserServiceImpl userServiceImpl = new UserServiceImpl();
userServiceImpl.setUserDao(userDao());
userServiceImpl.setMailSender(mailSender());
return userServiceImpl;
}
// <bean id="testUserService"
// class="springbook.user.service.UserServiceTest$TestUserServiceImpl"
// parent="userService" />
@Bean
@Qualifier("testUserService")
public UserServiceImpl testUserService() {
UserServiceImpl testUserServiceImpl = new UserServiceTest.TestUserServiceImpl();
testUserServiceImpl.setUserDao(userDao());
testUserServiceImpl.setMailSender(mailSender());
return testUserServiceImpl;
}
}
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = {TestServiceFactory.class})
public class UserServiceTest {
@Autowired UserService userService;
@Autowired UserService userServiceImpl;
@Autowired
@Qualifier("testUserService")
UserService testUserService;
// 생략 ...
}
AspectJ
AspectJ는 전통적인 Spring AOP의 동적 프록시 방식과는 다르게 작동합니다. Spring AOP가 프록시 기반의 접근 방식을 사용하는 것과 달리, AspectJ는 타깃 객체의 바이트코드를 직접 조작하는 방식으로 AOP를 구현합니다. 이러한 차이점은 AspectJ의 핵심 기능 중 하나로, 복잡한 바이트코드 조작을 통해 효과적으로 부가 기능을 타깃 오브젝트에 적용합니다.
AspectJ는 전통적인 Spring AOP의 동적 프록시 방식과는 다르게 작동합니다. Spring AOP가 프록시 기반의 접근 방식을 사용하는 것과 달리, AspectJ는 타깃 객체의 바이트코드를 직접 조작하는 방식으로 AOP를 구현합니다. 이러한 차이점은 AspectJ의 핵심 기능 중 하나로, 복잡한 바이트코드 조작을 통해 효과적으로 부가 기능을 타깃 오브젝트에 적용합니다.
AspectJ의 작동 방식
1. 바이트코드 조작: AspectJ는 컴파일된 클래스 파일 자체를 수정하거나, 클래스가 JVM에 로딩되는 시점에 바이트코드를 조작합니다. 이를 통해, 부가 기능을 타깃 객체의 클래스 정의에 직접 추가합니다.
2. 소스코드 직접 수정 없음: 이 과정은 타깃 객체의 소스코드를 직접 수정하지 않기 때문에, 개발자는 비즈니스 로직에만 집중할 수 있습니다. AspectJ의 바이트코드 조작은 런타임이나 컴파일 타임에 이루어지므로, 개발자가 작성한 원본 코드는 그대로 유지됩니다.
3. 직접적인 방법: AspectJ는 프록시를 사용하는 간접적인 방법 대신, 타깃 객체에 부가 기능을 직접 삽입하는 직접적인 방법을 사용합니다. 이는 프록시를 통한 메소드 호출이 아니라, 실제 객체의 메소드에 바로 부가 기능이 적용된다는 의미입니다.
AspectJ의 장점
- 성능: 바이트코드 조작을 통한 직접적인 방법은 프록시 기반의 AOP보다 성능상의 이점을 가질 수 있습니다.
- 유연성: 더 복잡하고 세밀한 AOP 시나리오를 구현할 수 있습니다. 예를 들어, 필드 접근, 메소드 호출, 객체 생성 등 다양한 조인 포인트에 대한 부가 기능을 적용할 수 있습니다.
AspectJ는 Spring AOP와 다르게 타깃 객체의 바이트코드를 직접 조작하여 AOP를 구현하는 방식을 사용합니다. 이는 프록시 기반의 방법보다 직접적이고 강력하며, 성능상의 이점과 함께 더 복잡한 AOP 시나리오를 가능하게 합니다. 개발자는 원본 소스코드를 수정할 필요 없이, AspectJ가 제공하는 고급 AOP 기능을 활용할 수 있습니다.
AspectJExpressionPointcut
org.springframework.aop.aspectj.AspectJExpressionPointcut
AspectJExpressionPointcut은 스프링 AOP에서 사용되는 포인트컷(pointcut)의 한 종류로, AspectJ의 표현식 언어를 사용하여 메소드 실행에 어드바이스(advice)를 적용할 지점을 선정합니다. 이는 스프링 AOP에서 AspectJ 스타일의 포인트컷 표현식을 사용할 수 있게 해주는 강력한 기능입니다.
Spring의 org.springframework.aop.Pointcut 구현은 AspectJ 위버를 사용하여 포인트컷 표현식(expression)을 평가합니다.
포인트컷 표현식 값은 AspectJ 표현식입니다. 이는 다른 포인트컷들을 참조하고 구성 및 기타 연산을 사용할 수 있습니다.
당연히, 이것은 Spring AOP의 프록시 기반 모델로 처리될 예정이므로, 오직 메소드 실행 포인트컷만 지원됩니다.
org.springframework.aop.Pointcut 핵심 Spring 포인트컷 추상화입니다.
포인트컷은 ClassFilter와 MethodMatcher로 구성됩니다.
위빙[weaving] 이란?
Pointcut에 의해 결정된 타겟의 JoinPoint에 부가기능(Advice)을 삽입하는 과정을 뜻한다.
"오직 메소드 실행 포인트컷만 지원됩니다"라는 문구는 Spring AOP의 한계와 특성을 설명하는 부분입니다.
AspectJ와 같은 AOP 구현체는 다양한 종류의 포인트컷을 지원합니다. 예를 들어, 메소드 호출, 생성자 호출, 필드 접근 등 다양한 프로그램 실행 지점(join points)에 대해 어드바이스(advice)를 적용할 수 있습니다. 그러나 Spring AOP는 프록시 기반의 AOP 구현을 사용합니다. 이는 Spring의 AOP 기능이 프록시 객체를 통해 메소드 호출을 가로채고 처리한다는 것을 의미합니다.
이 프록시 기반 모델은 다음과 같은 특성을 가집니다:
1. 메소드 실행 지점에만 초점: Spring AOP는 메소드 실행(execution)을 가로채는 포인트컷만 지원합니다. 즉, 메소드가 호출될 때 어드바이스가 적용될 수 있습니다.
2. 다른 종류의 포인트컷은 지원하지 않음: AspectJ와 같은 완전한 AOP 구현에서 제공하는 생성자 호출, 필드 접근, 객체 초기화 등과 같은 다른 종류의 포인트컷은 Spring AOP에서는 지원하지 않습니다.
이러한 제한은 Spring AOP가 가벼운 프록시 기반의 구현을 사용하기 때문에 발생합니다. 이 방식은 Spring 프레임워크의 경량성과 쉬운 통합을 가능하게 하지만, AspectJ와 같은 더 강력하고 유연한 AOP 구현에 비해 제한적인 기능을 가지게 됩니다. 따라서, 복잡하거나 다양한 AOP 기능이 필요한 경우, AspectJ와 같은 완전한 AOP 구현체를 사용하는 것이 더 적합할 수 있습니다.
public interface Pointcut {
/**
* Return the ClassFilter for this pointcut.
* @return the ClassFilter (never {@code null})
*/
ClassFilter getClassFilter();
/**
* Return the MethodMatcher for this pointcut.
* @return the MethodMatcher (never {@code null})
*/
MethodMatcher getMethodMatcher();
/**
* Canonical Pointcut instance that always matches.
*/
Pointcut TRUE = TruePointcut.INSTANCE;
}
public interface ExpressionPointcut extends Pointcut {
/**
* Return the String expression for this pointcut.
*/
@Nullable
String getExpression();
}
@SuppressWarnings("serial")
public abstract class AbstractExpressionPointcut implements ExpressionPointcut, Serializable {
@Nullable
private String location;
@Nullable
private String expression;
/**
* Set the location for debugging.
*/
public void setLocation(@Nullable String location) {
this.location = location;
}
@Nullable
public String getLocation() {
return this.location;
}
public void setExpression(@Nullable String expression) {
this.expression = expression;
try {
onSetExpression(expression);
}
catch (IllegalArgumentException ex) {
// Fill in location information if possible.
if (this.location != null) {
throw new IllegalArgumentException("Invalid expression at location [" + this.location + "]: " + ex);
}
else {
throw ex;
}
}
}
protected void onSetExpression(@Nullable String expression) throws IllegalArgumentException {
}
@Override
@Nullable
public String getExpression() {
return this.expression;
}
}
@SuppressWarnings("serial")
public class AspectJExpressionPointcut extends AbstractExpressionPointcut
implements ClassFilter, IntroductionAwareMethodMatcher, BeanFactoryAware {
private static final Set<PointcutPrimitive> SUPPORTED_PRIMITIVES = new HashSet<>();
// 생략 ...
@Nullable
private BeanFactory beanFactory;
@Nullable
private transient ClassLoader pointcutClassLoader;
@Nullable
private transient PointcutExpression pointcutExpression;
private transient Map<Method, ShadowMatch> shadowMatchCache = new ConcurrentHashMap<>(32);
public AspectJExpressionPointcut() {
}
public AspectJExpressionPointcut(Class<?> declarationScope, String[] paramNames, Class<?>[] paramTypes) {
this.pointcutDeclarationScope = declarationScope;
if (paramNames.length != paramTypes.length) {
throw new IllegalStateException(
"Number of pointcut parameter names must match number of pointcut parameter types");
}
this.pointcutParameterNames = paramNames;
this.pointcutParameterTypes = paramTypes;
}
@Override
public void setBeanFactory(BeanFactory beanFactory) {
this.beanFactory = beanFactory;
}
@Override
public ClassFilter getClassFilter() {
obtainPointcutExpression();
return this;
}
@Override
public MethodMatcher getMethodMatcher() {
obtainPointcutExpression();
return this;
}
// 생략 ...
}
AspectJExpressionPointcut의 핵심 기능
1. 표현식 기반의 포인트컷 정의: AspectJ 표현식을 사용하여, 어떤 클래스의 어떤 메소드에 어드바이스를 적용할 것인지 정의할 수 있습니다. 표현식을 통해 메소드 이름, 반환 타입, 매개변수 타입 및 수, 타겟 객체의 타입 등을 기준으로 세밀한 포인트컷을 설정할 수 있습니다.
2. 다양한 포인트컷 지시자 지원: AspectJ 표현식은 다양한 포인트컷 지시자(pointcut designator)를 지원합니다. 예를 들어, execution, within, this, target, args, @target, @args, @within, @annotation 등의 지시자를 사용하여 복잡한 조건을 정의할 수 있습니다.
3. 정교한 메소드 매칭: AspectJExpressionPointcut은 메소드의 시그니처(signature)와 호출되는 컨텍스트에 따라 어드바이스 적용 여부를 결정합니다. 이를 통해 매우 정교한 AOP 설정이 가능합니다.
AspectJExpressionPointcut의 사용 예시
- 메소드 이름 기반 포인트컷: execution(public * com.example.service.*.*(..))과 같은 표현식은 com.example.service 패키지 안의 모든 클래스의 모든 public 메소드에 어드바이스를 적용합니다.
- 특정 어노테이션이 적용된 메소드 선택: @annotation(com.example.MyAnnotation)과 같은 표현식은 MyAnnotation 어노테이션이 적용된 모든 메소드에 어드바이스를 적용합니다.
장점:
1. 유연성과 강력함: AspectJ의 표현식 언어를 사용함으로써, 매우 유연하고 강력한 방식으로 포인트컷을 정의할 수 있습니다.
2. 선언적인 접근 방식: XML 또는 어노테이션 기반의 설정을 통해 선언적으로 포인트컷을 정의할 수 있어, 코드의 가독성과 유지보수성이 향상됩니다.
3. 좀 더 복잡한 AOP 시나리오 대응 가능: 복잡한 비즈니스 로직에 대한 세밀한 AOP 설정이 가능하여, 개발자는 애플리케이션의 다양한 부분에 정교한 부가 기능을 적용할 수 있습니다.
주의사항
- 표현식의 복잡성: AspectJ 표현식은 매우 강력하지만, 복잡하고 세밀한 설정이 가능하기 때문에, 표현식을 잘못 작성하면 예상치 못한 부분에 AOP가 적용되거나, 반대로 필요한 부분에 적용되지 않을 수 있습니다. 따라서 표현식을 작성할 때는 정확한 문법과 의도한 바를 명확히 이해하는 것이 중요합니다.
- 성능 고려: AspectJ 표현식을 통한 AOP 구현은 일반적으로 성능에 크게 영향을 주지 않지만, 매우 복잡한 표현식이나 대규모 애플리케이션에서는 성능에 영향을 줄 수 있습니다. 따라서 성능에 민감한 애플리케이션에서는 성능 테스트를 통해 영향을 확인하는 것이 좋습니다.
- 디버깅과 테스팅: AOP가 적용된 코드는 디버깅과 테스팅이 일반적인 코드보다 복잡할 수 있습니다. 특히, AspectJ 표현식을 사용한 경우, 어디에 어떻게 AOP가 적용되는지 파악하기 어려울 수 있으므로, 적절한 로깅과 테스팅 전략이 필요합니다.
AspectJExpressionPointcut은 스프링 AOP에서 제공하는 강력하고 유연한 도구입니다. 이를 통해 개발자는 복잡한 비즈니스 요구사항에 맞춰 세밀하게 AOP를 적용할 수 있으며, 애플리케이션의 모듈성을 향상시킬 수 있습니다. 하지만, 이와 함께 오는 복잡성과 성능, 유지보수성 측면에서의 고려사항도 잘 인지하고 있어야 합니다.
@Bean
public AspectJExpressionPointcut transactionPointcut() {
AspectJExpressionPointcut aspectJExpressionPointcut =
new AspectJExpressionPointcut();
aspectJExpressionPointcut.setExpression("execution(* *..*ServiceImpl.upgrade*(..))");
return aspectJExpressionPointcut;
}
위 코드는 스프링의 @Bean 어노테이션을 사용하여 AspectJExpressionPointcut 객체를 생성하고 구성하는 메소드입니다. AspectJExpressionPointcut은 AOP에서 특정 조건에 부합하는 메소드에 어드바이스(Advice)를 적용하기 위한 포인트컷(Pointcut)을 정의합니다. 여기서 중요한 부분은 setExpression 메소드에 전달되는 AspectJ의 표현식입니다.
AspectJ 표현식 분석: execution(* *..*ServiceImpl.upgrade*(..))
1. execution: 이는 포인트컷 표현식의 가장 일반적인 형태로, 메소드 실행 시점을 지정합니다. execution 지시자는 어떤 메소드가 호출될 때 매칭되는지를 정의합니다.
2. *: 이는 반환 타입을 나타냅니다. 여기서 *는 모든 반환 타입을 의미합니다.
3. *..*ServiceImpl:
- *..*: 이 부분은 패키지명을 지정합니다. 여기서 *..*는 모든 패키지(*)와 그 하위 패키지(..)를 의미합니다. 즉, 어떤 패키지 구조에서도 해당됩니다.
- ServiceImpl: 클래스 이름 패턴을 나타냅니다. 이는 ServiceImpl로 끝나는 모든 클래스 이름에 매칭됩니다.
4. upgrade*: 메소드 이름 패턴을 나타냅니다. upgrade로 시작하는 모든 메소드 이름에 매칭됩니다.
5. (..): 이는 메소드 파라미터를 나타냅니다. 여기서 ..는 어떠한 타입과 수의 매개변수도 받을 수 있음을 의미합니다.
따라서, 이 표현식은 모든 반환 타입을 가진, 모든 패키지의 ServiceImpl로 끝나는 클래스 내의, upgrade로 시작하는 이름을 가진, 어떤 파라미터도 받을 수 있는 모든 메소드에 대한 실행을 포인트컷으로 지정합니다. 이는 매우 일반적으로 사용되는 AOP 설정으로, 특정한 패턴을 가진 메소드들에 대해 트랜잭션과 같은 부가적인 처리를 적용할 때 유용합니다.
TransactionInterceptor
이 코드는 스프링 프레임워크에서 트랜잭션 관리를 위한 TransactionInterceptor 빈을 설정하는 과정을 나타냅니다. TransactionInterceptor는 메소드 호출에 트랜잭션 관리 기능을 적용하기 위해 사용됩니다. 코드의 각 부분을 자세히 살펴보겠습니다.
@Bean
public TransactionInterceptor transactionAdvice() {
NameMatchTransactionAttributeSource txAttributeSource = new NameMatchTransactionAttributeSource();
RuleBasedTransactionAttribute readOnlyTx = new RuleBasedTransactionAttribute();
readOnlyTx.setReadOnly(true);
readOnlyTx.setTimeout(30);
RuleBasedTransactionAttribute readWriteTx = new RuleBasedTransactionAttribute();
readWriteTx.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
readWriteTx.setIsolationLevel(TransactionDefinition.ISOLATION_SERIALIZABLE);
txAttributeSource.addTransactionalMethod("get*", readOnlyTx);
txAttributeSource.addTransactionalMethod("*", readWriteTx);
return new TransactionInterceptor(transactionManager(), txAttributeSource);
}
코드 분석
1. NameMatchTransactionAttributeSource 생성
- NameMatchTransactionAttributeSource는 메소드 이름 패턴에 따라 트랜잭션 속성을 정의합니다.
2. 읽기 전용 트랜잭션 속성 설정 (readOnlyTx)
- readOnlyTx는 읽기 전용 트랜잭션 속성을 정의합니다.
- setReadOnly(true): 트랜잭션이 데이터를 수정하지 않고 오직 읽기만 할 것임을 나타냅니다.
- setTimeout(30): 트랜잭션의 타임아웃을 30초로 설정합니다.
3. 읽기/쓰기 트랜잭션 속성 설정 (readWriteTx)
- readWriteTx는 읽기와 쓰기가 가능한 트랜잭션 속성을 정의합니다.
- setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW): 새 트랜잭션을 시작하며, 이미 진행 중인 트랜잭션이 있으면 일시 중단시킵니다.
- setIsolationLevel(TransactionDefinition.ISOLATION_SERIALIZABLE): 가장 엄격한 격리 수준인 SERIALIZABLE을 설정합니다.
4. 트랜잭션 속성 메소드 매핑
- addTransactionalMethod("get*", readOnlyTx): 이름이 get으로 시작하는 모든 메소드에 대해 readOnlyTx 속성을 적용합니다.
- addTransactionalMethod("*", readWriteTx): 모든 메소드에 대해 readWriteTx 속성을 적용합니다. 이는 기본적으로 적용될 트랜잭션 속성을 정의합니다.
5. TransactionInterceptor 생성 및 반환
- new TransactionInterceptor(transactionManager(), txAttributeSource): TransactionInterceptor를 생성하며, 이는 txAttributeSource에 정의된 트랜잭션 속성을 기반으로 작동합니다. transactionManager() 메소드는 이 코드에서 정의되지 않았지만, 트랜잭션 매니저 빈을 제공하는 메소드임을 나타냅니다.
이 코드는 스프링의 선언적 트랜잭션 관리를 위한 설정으로, 특정 메소드 패턴에 따라 다른 트랜잭션 속성을 적용합니다. 읽기 전용과 읽기/쓰기 트랜잭션의 구분, 트랜잭션의 격리 수준과 전파 행위를 세밀하게 설정함으로써, 애플리케이션의 다양한 트랜잭션 요구사항을 효과적으로 관리할 수 있습니다. 이러한 설정은 애플리케이션의 데이터 일관성과 성능 최적화에 중요한 역할을 합니다.
특히, TransactionInterceptor는 AOP를 통해 메소드 호출에 트랜잭션 관리 기능을 적용하므로, 비즈니스 로직과 트랜잭션 관리 로직을 분리하여 코드의 가독성과 유지보수성을 개선합니다. 이는 스프링 프레임워크가 제공하는 선언적 트랜잭션 관리의 핵심적인 부분입니다.
'Spring Framework' 카테고리의 다른 글
PlatformTransactionManager (0) | 2024.04.09 |
---|---|
Spring Tool Suite 4 (0) | 2024.04.09 |
Spring ProxyFactoryBean (0) | 2024.04.09 |
Factory Bean (0) | 2024.04.09 |
Java Dynamic Proxy와 Spring FactoryBean (0) | 2024.04.09 |