트랜잭션 코드의 분리트랜잭션 경계 설정 코드 분리의 장점고립된 단위 테스트단위 테스트와 통합 테스트 둘 중 선택 가이드테스트에 대한 의견다이내믹 프록시와 팩토리 빈프록시와 프록시 패턴, 데코레이터 패턴다이내믹 프록시다이내믹 프록시를 위한 팩토리 빈프록시 팩토리 빈 방식의 장점과 한계스프링의 프록시 팩토리 빈ProxyFactoryBean어드바이스 : 타깃이 필요 없는 순수한 부가 기능 (MethodInterceptor → Advice)포인트컷 : 부가 기능 적용 대상 메소드 선정 방법스프링 AOP자동 프록시 생성빈 후처리기를 이용한 자동 프록시 생성기 (BeanPostProcessor )포인트컷 확장빈 등록(xml)포인트컷 표현식을 이용한 포인트컷문법포인트컷 표현식 이용해서 기존의 포인트컷 빈 변경AOP 란 무엇인가?부가기능의 모듈화AOP : 애스펙트 지향 프로그래밍AOP 적용기술프록시를 이용한 AOP바이트코드 생성과 조작을 통한 AOPAOP의 용어트랜잭션 속성, 포인트컷, 트랜잭션 지원 테스트
트랜잭션 코드의 분리
public void upgradeLevels() throws Exception { TransactionStatus status = this.transactionManager .getTransaction(new DefaultTransactionDefinition()); try { upgradeLevelsInternal(); this.transactionManager.commit(status); } catch (Exception e) { this.transactionManager.rollback(status); throw e; } } private void upgradeLevelsInternal() { List<User> users = userDao.getAll(); for (User user: users) { if (canUpgradeLevel(user)) { upgradeLevel(user); } } }
- 위와 같이 UserService 객체 안에서 트랜잭션 코드와 비지니스 로직을 분리했지만, 여전히 트랜잭션 코드가 UserService에 남아 있는 것이 거슬림 → 위임 기능을 가진 UserServiceTx를 만들어보자!

public interface UserService { void add(User user); void upgradeLevels(); } public class UserServiceImpl implements UserService { UserDao userDao; MailSender mailSender; public void upgradeLevels() { ... business Logic; } } public class UserServiceTx implements UserService { UserService userService; public void setUserService(UserService userService) { this.userService = userService; } public void add(User user) { userService.add(user); } public void upgradeLevels() { TransactionStatus status = this.transactionManager.getTransaction( new DefaultTransactionDefinition()); try { userService.upgradeLevels(); this.transactionManager.commit(status); } catch (RuntimeException e) { this.transactionManager.rollback(status); throw e; } } }
트랜잭션 경계 설정 코드 분리의 장점
- 비즈니스 로직을 담당하고 있는 UserServiceImpl의 코드를 작성할 때 트랜잭션과 같은 기술적인 내용에 전혀 신경쓰지 않아도 됨
- 트랜잭션의 적용이 필요한지도 신경 쓰지 않아도 됨
- 스프링의 JDBC나 JTA같은 로우레벨의 트랜잭션 API는 물론이고 스프링의 트랜잭션 추상화 API조차 필요 없음
- 비즈니스 로직에 대한 테스트를 손쉽게 만들어낼 수 있음
고립된 단위 테스트
- 처음부터 작은 단위로 테스트하면서 진행해 왔다면 나중에 덩치가 커져도 어렵지 않게 오류를 찾아낼 수 있음 ⇒ 테스트는 작은 단위로 하면 좋음
- 테스트 대상이 다른 오브젝트와 환경에 의존하고 있다면 작은 단위의 테스트가 주는 장점을 얻기가 힘듦
- 테스트를 의존 대상으로부터 분리해서 고립시키는 방법은 MailSender에 적용해봤던 대로 테스트를 위한
대역
을 사용하는 것임
- 고립된 테스트를 만드는 것의 장점
- 다른 의존 대상에 영향을 받을 경우를 대비해 복잡하게 준비할 필요가 없음
- 테스트 수행 성능도 크게 향상됨
단위 테스트와 통합 테스트 둘 중 선택 가이드
- 항상 단위 테스트를 먼저 고려
- 하나의 클래스나 성격과 목적이 같은 긴밀한 클래스 몇 개를 모아서 외부와의 의존관계를 모두 차단하고 필요에 따라 스텁이나 목 오브젝트 등의 테스트 대역을 이용하도록 테스트를 만든다. 단위 테스트는 테스트 작성도 간단하고 실행 속도도 빠르며 테스트 대상 외의 코드나 환경으로부터 테스트 결과에 영향 받지 않기에 빠른 시간에 효과적인 테스트 작성에 유리함
- 외부 리소스를 사용해야만 가능한 테스트는 통합 테스트로.
- DAO는 DB까지 연동하는 테스트로 만드는 편이 효과적임. 외부 리소스를 사용하기에 통합 테스트로 분류되지만, 코드에서 보면 하나의 기능 단위를 테스트 하는 것이라고 볼 수 있음
- 여러 개의 단위가 의존관계를 가지고 동작할 때를 위한 통합 테스트는 필요함. 다만, 단위 테스트를 충분히 거쳤다면 통합 테스트의 부담은 상대적으로 줄어듦
- 단위 테스트를 만들기가 너무 복잡하다고 판단되는 코드는 처음부터 통합 테스트를 고려해 본다. 이때도 통합 테스트에 참여하는 코드 중에서 가능한 한 많은 부분을 미리 단위 테스트로 검증해두는 것이 유리함
- 스프링 테스트 컨텍스트 프레임워크를 이용하는 테스트는 통합 테스트. 가능하면 스프링의 지원 없이 직접 코드 레벨의 DI를 사용하면서 단위 테스트를 하는 것이 좋지만 스프링의 설정 자체도 테스트 대상이고, 스프링을 이용해 좀 더 추상적인 레벨에서 테스트해야 할 경우도 종종 있기에
테스트에 대한 의견
- TDD는 코드를 만들자마자 바로 테스트가 가능하다는 장점이 있음.
- 테스트를 코드를 작성한 후에 만드는 경우에도 가능한 한 빨리 작성하도록 해야 함
- 코드를 작성하면서 테스트는 어떻게 만들 수 있을까를 생각해보는 것은 좋은 습관이다.
- 스프링이 지지하고 권장하는 깔끔하고 유연한 코드를 만들다보면 테스트도 그만큼 만들기 쉬워지고, 테스트는 다시 코드의 품질을 높여주고, 리팩토링과 개선에 대한 용기를 주기도 함
- 반대로 좋은 코드를 만드려는 노력을 게을리하면 테스트 작성이 불편해지고, 테스트를 잘 만들지 않게 될 가능성이 높아짐
다이내믹 프록시와 팩토리 빈
프록시와 프록시 패턴, 데코레이터 패턴
- 위의
UserServiceTx
와UserServiceImpl
을 보면 부가기능(트랜잭션) 외의 나머지 모든 기능은 원래 핵심기능을 가진 클래스로 위임해줌
- 핵심기능은 부가기능을 가진 클래스의 존재 자체를 모른다. 따라서 부가기능이 핵심기능을 사용하는 구조가 되는 것

- 클라이언트는 인터페이스만 보고 사용을 하기 때문에 자신이 핵심기능을 가진 클래스를 사용할 것이라고 기대하지만, 사실 위의 그림처럼 부가기능을 통해 핵심기능을 이용하게 되는 것 →
프록시
(대리자, 대리인)
- 프록시를 통해 최종적으로 요청을 위임받아 처리하는 실제 오브젝트를
타깃(target)
, 또는 실체라고 부름 - 프록시의 특징은 타깃과 같은 인터페이스를 구현했다는 것
- 프록시가 타깃을 제어할 수 있는 위치에 있다는 것
다이내믹 프록시
Java Dynamic Proxy (JDK Proxy 🆚 CGLib) (프록시 🆚 프록시 패턴 🆚 데코레이터 패턴)
TransactionHandler 예제 코드
public class TransactionHandler implements InvocationHandler { Object target; PlatformTransactionManager transactionManager; String pattern; public void setTarget(Object target) { this.target = target; } public void setTransactionManager( PlatformTransactionManager transactionManager) { this.transactionManager = transactionManager; } public void setPattern(String pattern) { this.pattern = pattern; } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if (method.getName().startsWith(pattern)) { return invokeInTransaction(method, args); } else { return method.invoke(target, args); } } private Object invokeInTransaction(Method method, Object[] args) throws Throwable { TransactionStatus status = this.transactionManager .getTransaction(new DefaultTransactionDefinition()); try { Object ret = method.invoke(target, args); this.transactionManager.commit(status); return ret; } catch (InvocationTargetException e) { this.transactionManager.rollback(status); throw e.getTargetException(); } } }
invoke()
에, 타깃을 선별하는 역할 포함(메서드의 이름과 패턴 비교)
invokeInTransaction()
에 부가기능 역할 포함
다이내믹 프록시를 위한 팩토리 빈
- 일반적으로 다이내믹 프록시 오브젝트는 스프링의 빈으로 등록할 방법이 없음. 사전에 프록시 오브젝트의 클래스 정보를 미리 알아내서 스프링의 빈에 정의할 방법이 없기에. (Proxy의 newProxyInstance() 스태틱 팩토리 메서드로만 만들 수 있음) ⇒ 팩토리 빈을 사용하자!
팩토리 빈이란 스프링을 대신해서 오브젝트의 생성로직을 담당하도록 만들어진 특별한 빈 [ 예제 코드 첨부. TxProxyFactoryBean
]
public interface FactoryBean<T> { T getObject() throws Exception; // 빈 오브젝트 생성해서 돌려줌 Class<? extends T> getObjectType(); // 생성되는 오브젝트의 타입을 알려줌 boolean isSingleton(); // getObject() 가 돌려주는 오브젝트가 항상 같은 싱글톤 오브젝트인지 알려줌 }
public class TxProxyFactoryBean implements FactoryBean<Object> { Object target; PlatformTransactionManager transactionManager; String pattern; Class<?> serviceInterface; public void setTarget(Object target) { this.target = target; } public void setTransactionManager(PlatformTransactionManager transactionManager) { this.transactionManager = transactionManager; } public void setPattern(String pattern) { this.pattern = pattern; } public void setServiceInterface(Class<?> serviceInterface) { this.serviceInterface = serviceInterface; } // FactoryBean 인터페이스 구현 메소드 public Object getObject() throws Exception { TransactionHandler txHandler = new TransactionHandler(); txHandler.setTarget(target); txHandler.setTransactionManager(transactionManager); txHandler.setPattern(pattern); return Proxy.newProxyInstance( getClass().getClassLoader(),new Class[] { serviceInterface }, txHandler); } public Class<?> getObjectType() { return serviceInterface; } public boolean isSingleton() { return false; } }
<bean id="userService" class=springbook.user.service.TxProxyFactoryBean"> <property name="target" ref="userServiceImpl" /> <property name="transactionManager" ref="transactionManager" /> <property name="pattern" value="upgradeLevels" /> <property name="serviceInterface" value="springbook.user.service.UserService" /> </bean>
프록시 팩토리 빈 방식의 장점과 한계
장점
- 타깃 오브젝트에 맞는 프로퍼티 정보를 설정해서 빈으로 등록만 하면 다이내믹 프록시를 생성해주는 TxProxyFactoryBean을 코드 수정없이 재사용 할 수 있다.
<bean id="coreServiceTarget" class="complex.module.CoreServiceImpl"> <property name="coreDao" ref="coreDao" /> </bean> <bean id="coreService" class="springbook.service.TxProxyFactoryBean"> <property name="target" ref="coreServiceTarget" /> <property name="transactionManager" ref="transactionManager" /> <property name="pattern" value="" /> <!-- 모든 메서드에 트랜잭션 적용 --> <property name="serviceInterface" value="complex.module.CoreService" /> </bean>
- 프록시를 적용할 대상이 구현하고 있는 인터페이스를 구현하는 프록시 클래스를 일일히 만들 필요 없음
- 부가적인 기능이 여러 메서드에 나타나지 않고
InvocationHandler
에서만 나타남
한계
- 프록시를 통해 타깃에 부가기능을 제공하는 것은 메소드 단위로 일어나는 일. 하나의 클래스 안에 존재하는 여러 개의 메소드에 부가기능을 한 번에 제공하는 것은 어렵지 않게 가능했음.
- 하지만, 한 번에 여러 개의 클래스에 공통적인 부가기능을 제공하는 일은 지금까지 살펴본 방법으로는 불가능함
- 하나의 타깃 오브젝트에만 부여되는 부가기능이라면 상관없지만, 트랜잭션과 같이 비지니스 로직을 담은 많은 클래스의 메소드에 적용할 필요가 있다면 거의 비슷한 프록시 팩토리 빈 설정의 중복을 막을 수 없음
- 하나의 타깃에 여러 개의 부가기능을 적용하려고 할 때도 문제임. 부가 기능이 붙을 때마다, 설정이 또 추가되어야 함
- 또 한가지 문제점 :
TransactionHandler
오브젝트가 프록시 팩토리 빈 개수만큼 만들어진다는 것 - 타깃 오브젝트가 달라지면 트랜잭션 부가기능을 제공하는 동일한 코드임에도
TransactionHandler
오브젝트를 새로 만들어야 함
스프링의 프록시 팩토리 빈
ProxyFactoryBean
- 스프링은 트랜잭션 기술과 메일 발송 기술에 적용했던 서비스 추상화를 프록시 기술에도 동일하게 적용하고 있음
- 자바에는 JDK Dynamic Proxy 이외에도 편리하게 프록시를 만들 수 있도록 지원해주는 다양한 기술이 존재함. 따라서 스프링은 일관된 방법으로 프록시를 만들 수 있게 도와주는 추상 레이어를 제공해줌
- 위의
TxProxyFactoryBean
과 달리ProxyFactoryBean
은 순수하게 프록시를 생성하는 작업만을 담당하고 프록시를 통해 제공하는 부가기능은 별도의 빈(MethodInterceptor
)에 둘 수 있음 ProxyFactoryBean
이 생성하는 프록시에서- 부가기능 :
MethodInterceptor
인터페이스를 구현해서 생성 MethodInterceptor
와InvocationHandler
와 다른점InvocationHandler
의invoke()
메소드는 타깃 오브젝트에 대한 정보를 제공하지 않기에 InvocationHandler를 구현한 클래스가 타깃을 갖고 있어야 함MethodInterceptor
의invoke()
메서드는ProxyFactoryBean
으로 부터 타깃 오브젝트에 대한 정보까지도 함께 제공받음 ⇒ 타깃 오브젝트에 상관없이 독립적으로 만드러질 수 있기에 여러 프록시에서 함께 사용할 수 있게 됨
@Test public void proxyFactoryBean() { ProxyFactoryBean pfBean = new ProxyFactoryBean(); /* 타깃 설정. InvocationHandler와 달리, MethodInterceptor에는 타깃을 넣어줄 필요 없음 */ pfBean.setTarget(new HelloTarget()); pfBean.addAdvice(new UppercaseAdvice()); // 부가기능을 담은 어드바이스 추가. 여러개 추가 가능 Hello proxiedHello = (Hello) pfBean.getObject(); } static class UppercaseAdvice implements MethodInterceptor { public Object invoke(MethodInvocation invocation) throws Throwable { String ret = (String) invocation.proceed(); return ret.toUpperCase(); }
어드바이스 : 타깃이 필요 없는 순수한 부가 기능 (MethodInterceptor
→ Advice
)
MethodInterceptor
는InvocationHandler
와 달리, 메소드 정보와 함께 타깃 오브젝트 정보가 담긴MethodInvocation
오브젝트가 전달됨
MethodInvocation
은 타깃 오브젝트의 메소드를 실행할 수 있는 기능이 있기 때문에MethodInterceptor
는 부가기능을 제공하는 데만 집중할 수 있음
MethodInterceptor
의 구현 클래스는 일종의 공유 가능한 템플릿처럼 동작하는 것.MethodInvocation
이 일종의 콜백 오브젝트가 되는 것. ⇒ 이 점이 JDK 다이내믹 프록시를 직접 사용하는 코드와 스프링이 제공해주는 프록시 추상화 기능인ProxyFactoryBean
을 사용하는 코드의 가장 큰 차이점이자 장점- 재사용 가능한 기능을 만들어두고 바뀌는 부분(
콜백 오브젝트와 메소드 호출정보
)만 외부에서 주입해서 이를 작업 흐름(부가기능 부여
) 중에 사용하도록 하는 전형적인 템플릿/콜백 구조
addAdvice()
⇒ 여러 개의 MethodInterceptor를 추가할 수 있음- MethodInterceptor는 Advice 인터페이스를 상속하고 있는 서브 인터페이스.
- MethodInterceptor 처럼 타깃 오브젝트에 적용하는 부가기능을 담은 오브젝트를 스프링에서는 어드바이스 라고 부름.
- JDK 다이내믹 프록시에서는 있었던 프록시가 구현해야 할 인터페이스 정보 넘겨주는 부분이 ProxyFactoryBean 구성에서는 없어짐
- 인터페이스 자동검출 기능이 있어 타깃 오브젝트가 구현하고 있는 인터페이스 정보를 알아냄
- 알아낸 인터페이스를 모두 구현하는 프록시를 만들어줌
- 타깃 오브젝트가 구현하는 인터페이스 중에서 일부만 프록시에 적용하기를 원한다면 인터페이스 정보를 직접 제공해주어도 됨.
포인트컷 : 부가 기능 적용 대상 메소드 선정 방법
- 기존의
InvocationHandler
에는 부가기능 + 메소드 선정 역할 2개가 같이 있었음
- 부가 기능 적용 대상 메소드 선정방식을 MethodInterceptor에 포함시키기에는 타깃 오브젝트가 계속 바뀌기 때문에 문제가 됨
- 스프링은 부가기능을 제공하는 오브젝트를
어드바이스
, 메소드 선정 알고리즘을 담은 오브젝트를포인트컷
이라고 부름

- 프록시로부터 어드바이스와 포인트컷을 독립시키고 DI를 사용하게 한 것은 전형적인 전략 패턴 구조임. 덕분에 여러 프록시가 공유해서 사용할 수도 있고, 또 구체적인 부가기능 방식이나 메소드 선정 알고리즘이 바뀌면 구현 클래스만 바꿔서 설정에 넣어주면 됨
@Test public void pointcutAdvisor() { ProxyFactoryBean pfBean = new ProxyFactoryBean(); pfBean.setTarget(new HelloTarget()); NameMatchMethodPointcut pointcut = new NameMatchPointcut(); pointcut.setMappedName("sayHi"); pfBean.addAdvisor(new DefaultPointcutAdvisor(pointcut, new UppercaseAdvice()));
Advisor
=Advice
+Pointcut
<bean id="transactionAdvice" class="springbook.user.service.TransactionAdvice"> <property name="transactionManager" ref="transactionManager" /> </bean> <bean id="transactionPointcut" class="org.springframeweork.aop.support.NameMatchMethodPointcut"> <property name="mappedName" value="upgrade*" /> </bean> <bean id="transactionAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor"> <property name="advice" ref="transactionAdvice" /> <property name="pointcut" ref="transactionPointcut" /> </bean> <bean id="userService" class="org.springframework.aop.framework.ProxyFactoryBean"> <property name="target" ref="userServiceImpl" /> <property name="interceptorNames"> <list> <value>transactionAdvisor</value> </list> </property> </bean>
스프링 AOP
자동 프록시 생성
- ProxyFactoryBean을 이용하여 설정 파일에 각 target 별로 Proxy를 등록할 수 있었음
- 그러나 이 또한 새로운 타깃이 생성될 때, ProxyFactoryBean 설정을 추가해야 하는 번거로움이 있음
- target 프로퍼티를 제외하면 다른 부분은 계속 동일하기에 단순하고 쉬운 작업. 그러나 그에 따라 실수가 발생할 수 있음
<bean id="userService" class="org.springframework.aop.framework.ProxyFactoryBean"> <property name="target" ref="userServiceImpl"> <property name="interceptorNames"> <!-- 부가 기능 리스트 --> <list> <value>tansactionAdvisor</value> </list> </property> </bean>
- 이제까지의 중복 문제 접근 방법을 보면
변하지 않는 타깃으로의 위임과 부가기능 적용 여부 판단
이라는 부분은 코드 생성 기법을 이용하는 다이내믹 프록시 기술에 맡기고,변하는 부가기능 코드
는 별도로 만들어서 다이내믹 프록시 생성 팩토리에 DI로 제공하는 방법을 사용하였음.
빈 후처리기를 이용한 자동 프록시 생성기 (BeanPostProcessor
)
- 스프링은 OCP의 가장 중요한 요소인 유연한 확장이라는 개념을 스프링 컨테이너 자신에게도 다양한 방법으로 적용하고 있음. 스프링의 DI를 이용해 만들어지는 애플리케이션의 코드가 OCP에 충실할 수 있다면 스프링 스스로도 그런 가치를 따르는게 마땅. 그래서 스프링은 컨테이너로서 제공하는 기능 중에서 변하지 않는 핵심적인 부분 외에는 대부분 확장할 수 있도록 확장 포인트를 제공해줌.
- 여기서는
DefaultAdvisorAutoProxyCreator
를 이용하여 프록시를 자동 생성함 (BeanPostProcessor
interface의 구현체) - 등록된 빈 중
Advisor
인터페이스 구현한 것 모두 찾음 - 그리고 생성되는 모든 빈에 대해 어드바이저의 포인트컷을 적용해보면서 프록시 적용 대상 선정
- 해당 빈 후처리기가 등록되어 있으면, 스프링은 빈 오브젝트가 생성될 때마다 빈 후처리기에 보내어 후처리 작업을 요청함
- 빈 후처리기는 빈 오브젝트의 프로퍼티를 강제로 수정할 수도 있고 별도의 초기화 작업을 수행할 수도 있음 ⇒ 이를 이용하여 스프링이 생성하는 빈 오브젝트의 일부를 프록시로 포장 or 프록시를 빈으로 대신 등록이 가능함

- ProxyFactoryBean에 프로퍼티로 설정된 어드바이저에게 생성된 빈이 프록시 생성을 해야 하는 빈인지 확인
- 1 — 맞다면 프록시 생성 2 — 대상이 아니라면 프록시 생성x
- 대상이 맞을 때, 프록시 생성 후 어드바이저 연결하여 빈으로 등록
- 빈 반환
포인트컷 확장
- 앞에서 포인트 컷을 메소드 선정에만 사용했지만, 어떤 클래스에 적용할 지의 기능도 가질 수 있음
public interface Pointcut { ClassFilter getClassFilter(); // 프록시를 적용할 클래스인지 확인 MethodMatcher getMethodMatcher(); // 어드바이스를 적용할 메소드인지 확인 }
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()); } } }
빈 등록(xml)
어드바이저를 사용하는 자동 프록시 생성기 등록
<bean class= "org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/>
포인트컷 빈, 어드바이스 빈, 어드바이저 빈
<bean id="transactionAdvice" class="springbook.user.service.TransactionAdvice"> <property name="transactionManager" ref="transactionManager" /> </bean> <bean id="transactionPointcut" class="springbook.service.NameMatchClassMethodPointcut"> <property name="mappedClassName" value="*ServiceImpl" /> <property name="mappedName" value="upgrade*" /> </bean> <bean id="transactionAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor"> <property name="advice" ref="transactionAdvice" /> <property name="pointcut" ref="transactionPointcut" /> </bean>
포인트컷 표현식을 이용한 포인트컷
- 앞에서의
NameMatchClassMethodPointcut
은 클래스와 메소드의 이름의 패턴을 독립적으로 비교하도록 만들어져 있음
AspectJExpressionPointcut
은 클래스와 메소드의 선정 알고리즘을 포인트컷 표현식을 이용해 한번에 지정 가능함
문법
execution([접근제한자 패턴] 반환 타입패턴 [타입패턴.]이름패턴 (파라미터 타입패턴 | ".." , ...) [throws 예외 패턴]) # 메소드의 풀 시그니쳐와 비교하면 알아보기 쉬움 # public int springbook.learningtest.spring.pointcut.Target.minus(int, int) # throws java.lang.RuntimeException
- 접근제한자(생략 가능) :
public
,private
,protected
- 반환 타입패턴 :
int
- 타입패턴. (생략 가능) : springbook.learningtest.spring.pointcut.Target
- 패키지이름과 클래스, 인터페이스 이름에 *를 사용할 수 있음.
..
를 사용하면 한 번에 여러 개의 패키지 선택 가능함 - 클래스 이름 패턴이 아닌 타입패턴이므로 해당 클래스의 상위 클래스로 명시되어 있어도 매칭이 됨
execution(* *..*ServiceImpl.upgrade*(..))
- 위의 표현식일 때,
UserServiceImpl
을 구현하는TestUserService
도 해당이 됨. 타입으로 봤을 때UserServiceImpl
타입도 되기에
- 이름패턴 :
minus
- 파라미터 타입패턴 : (int, int)
- .. : 타입과 개수에 상관없이 모두 다 허용하는 패턴
- … : 뒷 부분의 파라미터 조건은 생략
- 예외 패턴(생략 가능)
포인트컷 표현식 이용해서 기존의 포인트컷 빈 변경
<bean id="transactionPointcut" class="springbook.service.NameMatchClassMethodPointcut"> <property name="mappedClassName" value="*ServiceImpl" /> <property name="mappedName" value="upgrade*" /> </bean> <bean id="transactionPointcut" class="org.springframework.aop.aspectj.AspectJExpressionPointcut"> <property name="expression" value="execution(* *..*ServiceImpl.upgrade*(..))" /> </bean>
AOP 란 무엇인가?
UserService에 트랜잭션을 적용해온 과정
트랜잭션 서비스 추상화
트랜잭션 적용이라는 추상적인 작업 내용은 유지한 채로 구체적인 구현 방법을 자유롭게 바꿀 수 있도록 서비스 추상화 기법 적용 (
PlatformTransactionManager
)프록시와 데코레이터 패턴
- 트랜잭션을 어떤 기술을 사용해 적용할 것인가는 추상화를 통해 코드에서 제거했지만, 여전히 비즈니스 로직에 트랜잭션을 적용하고 있다는 것이 보임 → DI를 이용해 데코레이터 패턴으로 부가 기능을 비지니스 로직에서 분리
- 클라이언트가 일종의 대리자인 프록시 역할을 하는 트랜잭션 데코레이터를 거쳐서 타깃에 접근하도록 함
다이나믹 프록시와 프록시 팩토리 빈
- 프록시 클래스를 만드는 작업이 큰 짐 → 프록시 클래스 없이도 프록시 오브젝트를 런타임 시에 만들어주는 JDK 다이내믹 프록시 기술 적용
- 일부 메소드에만 트랜잭션 적용하는 경우는 메소드 선정 패턴을 이용할 수 있었지만 동일한 기능의 프록시를 여러 오브젝트에 적용할 경우 오브젝트 단위로 중복이 일어나는 문제는 해결하지 못함
- 스프링의
ProxyFactoryBean
을 사용하여 다이내믹 프록시 생성 방법에 DI 도입 - 부가기능을 담은
어드바이스
와 부가기능 선정 알고리즘을 담은포인트컷
프록시에서 분리
⇒ 여러 프록시에서 공유
자동 프록시 생성 방법과 포인트 컷
- 스프링 컨테이너의 빈 생성 후처리 기법(
BeanPostProcessor
) 활용하여 자동으로 프록시 만들어주는 방법 도입
- 클래스를 선정하는 기능을 담은 확장된 포인트컷 사용하여 트랜잭션 부가기능을 어디에 적용하는 지에 대한 정보를 포인트 컷이라는 독립적인 정보로 완전히 분리함
부가기능의 모듈화
- 관심사가 같은 코드를 분리해 한데 모으는 것은 소프트웨어 개발의 가장 기본이 되는 원칙임
- 그러나 트랜잭션 적용 코드는 기존에 써왔던 방법으로는 간단하게 분리해서 독립된 모듈로 만들 수가 없음 ← 트랜잭션 경계설정 기능은 다른 모듈의 코드에 부가적으로 부여되는 기능이라 한데 모을 수 없고 어플리케이션 전반에 여기저기 흩어져 있기 때문 ⇒ 그래서 다이내믹 프록시라든가 IoC/DI 컨테이너의 빈 생성 작업을 가로채서 빈 오브젝트를 프록시로 대체하는 빈 후처리 기술과 같은 복잡한 기술이 요구됨
트랜잭션 부가기능이란 스스로 독립적인 방식으로 존재해서는 적용되기 어려움. 타깃이 존재해야만 의미가 있기에 이를 독립적인 모듈로 어떻게 만들지를 고민했던 것
- DI
- 데코레이터 패턴
- 다이내믹 프록시
- 오브젝트 생성 후처리기
- 자동 프록시 생성
- 포인트 컷
- 위의 모든 작업이 핵심기능에 부여되는 부가기능을 효과적으로 모듈화하는 방법을 찾는 것이었음
AOP : 애스펙트 지향 프로그래밍
- 전통적인 객체지향 기술의 설계 방법으로는 독립적인 모듈화가 불가능한 트랜잭션 경계설정과 같은 부가기능을 어떻게 모듈화 할 것인가를 연구해온 사람들은, 이 부가기능 모듈화 작업은 기존의 객체 지향 설계 패러다임과는 구분되는 새로운 특성이 있다고 생각
- 그래서 이런 부가기능 모듈을 객체지향 기술에서 주로 사용하는 오브젝트와는 다르게 특별한 이름으로 부르기 시작했는데 ⇒
Aspect
- 에스팩트란 그 자체로 애플리케이션의 핵심기능을 담고 있지는 않지만, 애플리케이션을 구성하는 중요한 한 가지 요소이고, 핵심기능에 부가되어 의미를 갖는 특별한 모듈을 가리킴
- 애플리케이션의 핵심적인 기능에서 부가적인 기능을 분리해서 에스펙트라는 독특한 모듈로 만들어 설계하고 개발하는 방법을 Aspect Oriented Programming 이라고 부름
AOP 적용기술
프록시를 이용한 AOP
- 프록시로 만들어서 DI로 연결된 빈 사이에 적용해 타깃의 메소드 호출 과정에 참여하여 부가기능을 제공해주는 방식
MethodInterceptor
(→Interceptor
→Advice
) 인터페이스는 다이내믹 프록시의 InvoactionHandler와 마찬가지로 프록시로부터 메소드 요청정보를 전달받아 타깃 오브젝트의 메서드를 호출
바이트코드 생성과 조작을 통한 AOP
- AOP 기술의 원조이자 가장 강력한 AOP 프레임워크로 꼽히는 AspectJ에서 사용하는 방식. 프록시 사용안함
- AspectJ는 프록시처럼 간접적인 방법이 아니라, 타깃 오브젝트를 뜯어고쳐서 부가 기능을 직접 넣어주는 방법을 사용함
- 컴파일된 타깃의 클래스 파일 자체를 수정하거나
- 클래스가 JVM에 로딩되는 시점을 가로채서 바이트코드를 조작하는 복잡한 방법을 사용함
- 프록시 사용안하고 이렇게 하는 이유
- 바이트코드 조작해서 직접수정해버리면 DI 컨테이너의 도움을 받아 자동 프록시 생성 방식을 사용하지 않아도 AOP 적용이 가능하기 때문
- 프록시 방식보다 훨씬 강력하고 유연한 AOP가 가능함
- 프록시 방식으로 하면 클라이언트가 호출할 때 사용하는 메소드로 제한됨
- 바이트코드 직접 조작하면 오브젝트 생성, 필드 값 조회와 조작, 스태틱 초기화 등 다양한 작업에 부가 기능을 부여할 수 있음
AOP의 용어
- 타깃 : 부가기능을 부여할 대상. 핵심기능을 담은 클래스이거나 다른 부가기능을 제공하는 프록시 오브젝트
- 어드바이스 : 타깃에게 제공할 부가기능을 담은 모듈
- 조인 포인트 : 어드바이스가 적용될 수 있는 위치. 스프링의 프록시 AOP 에서는 조인 포인트는 메소드의 실행 단계 뿐임
- 포인트컷 : 어드바이스를 적용할 조인포인트를 선별하는 작업 또는 그 기능을 정의한 모듈
- 프록시 : 클라이언트와 타깃 사이에 투명하게 존재하며 부가기능을 제공하는 오브젝트
- 어드바이저 : 포인트컷과 어드바이스를 하나씩 갖고 있는 오브젝트. 어드바이저는 어떤 부가기능(어드바이스)을 어디에(포인트컷) 전달할 것인가를 알고 있는 AOP의 가장 기본이 되는 모듈
- 에스펙트: OOP의 클래스와 마찬가지로 에스펙트는 AOP의 기본 모듈. 한 개 또는 그 이상의 포인트컷과 어드바이스의 조합으로 만들어지며 보통 싱글톤 형태의 오브젝트로 존재함