6. AOP
AOP
AOP는 IoC/DI, 서비스 추상화와 함께 Spring 3대 기반 기술이다 관점 지향 프로그래밍... 트랜잭션에서 시작 트랜잭션 코드와 비즈니스 코드의 분리 -> 여기에서 트랜잭션 코드 같은 경우는 동일하게 사용될 수 있다. 분리를 하여 따로 작성하면서 적절히 트랜잭션에 대한 처리!
여기에서 등장하는 개념이 AOP
@Autowired의 문제점
type 기반으로 빈을 찾음-> 상위타입 Autowired일 경우 하위타입이 2개이상이면 에러 발생.. (애매하다고 알려줌)
고립된 단위 테스트
테스트의 가장 좋은 단위는 가능한 가장 작게 쪼개서 테스트
테스트 실패시 원인 분석이 빠름
테스트 대상의 의존대상
A를 테스트하고 싶은데 A와 관계를 갖는 B,C,D 클래스가 존재하는 경우 그리고 B,C,D는 또 각각 의존관계를 갖는 다른 클래스도 존재 할 것이다. 즉 여러곳에 영향을 받거나 미치고 있는셈.. 이럴 경우 테스트는 A를 하고싶었는데.. 다른게 더 신경쓰이게 되버리는 현상이 나오는 것이다.
테스트 대상 오브젝트 고립
그래서 테스트의 대상이 환경, 외부서버, 다른클래스의 코드에 종속되고 영향을 받지 않도록 고립시켜야한다.
-> 목오브젝트 작성을 하여 적절히 구현 -> 구현시 사용안하는 메서드는
// 사용안하는 메서드 : 예외 처리 -> 사용안하는 거라고 명시
public void add(){
throw new UnsupportedOperationException()
}단테, 통테
단테의 경우를 항상 먼저 생각, 외부 리소스를 사용해야하는 경우 통테, 단테의 어려움 (ex DAO)도 존재.
스프링 테스트 컨텍스트 프레임워크 -> 통합테스트
Mockito 프레임워크
목의 기반이 되는 개념이 프록시의 스텁 같은 느낌
목오브젝트를 일일이 작성하는 것을 대신해주는 프레임워크, 즉 기존처럼 작성할 필요가 없음!! 개신박함!!!
Java 클래스 임포트
라이브러리 maven 추가
다이내믹 프록시와 팩토리빈
기본 사용법 mock, when, thenReturn, verify, times, any 메서드
데코레이터 데코레이터 패턴.. 디패에서 배웠던 내용 요약.. 동적으로 기능을 추가할 수 있게 구현된 패턴
프록시 프록시, 대리인 역활, 접근제어, 스텁과 목의 기반이되는 개념 RMI 등 원격 프록시 타겟 인터페이스에 위임, 실제 필요한 경우 실제 object를 호출
리플렉션 클래스에 대한 정보를 얻거나, 인스턴스 생성 등 동적으로 구현할 수 있음
X.class 나 x.getClass()를 활용하여Class 클래스를 통해 정보를 얻을 수 있음다이나믹 proxy 클래스
```java
// Hello 인터페이스 public interface Hello { String sayHello(String name);
String sayHi(String name);
String sayThankYou(String name); }
// 타겟 오브젝트 public class HelloTarget implements Hello { @Override public String sayHello(String name) { return "Hello " + name; }
@Override public String sayHi(String name) { return "Hi " + name; }
@Override public String sayThankYou(String name) { return "Thank You " + name; } }
// 다이나믹 Proxy에서 사용할 부가기능 : InvocationHandler를 꼭 구현해야함 public class UppercaseHandler implements InvocationHandler { Hello target; // 타겟 인터페이스
public UppercaseHandler(Hello target) { this.target = target; }
@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { String ret = (String) method.invoke(target, args); return ret.toUpperCase(); } }
다이나믹 Proxy의 단점은... 타깃 오브젝트가 늘어나면 그만큼 Proxy를 만들어줘야하는... 상황이 발생... -> 그렇게되다보면 설정 부분도 중복... -> 해결책은 ? 팩토리빈 -> 팩토리빈 -> 스프링팩토리빈
스프링의 프록시 팩토리 빈
프록시 오브젝트를 생성해주는 기술을 추상화한 팩토리 빈
ProxyFactoryBean
순수하게 Proxy 생성만 담당. 기존에 트랜잭션 부가기능이 들어간 만든 TxProxyFactoryBean하고는 기능만 다름
즉 순순 Proxy 생성만 담당 / 부가기능은 따로 정의하여 사용
기능과 역활의 분리.... 미친듯한 설계.. 감탄
org.aopalliance.intercept.MethodInterceptor extends Advice
부가기능은
MethodInterceptor 인터페이스 구현을 통해 만듬InvocationHandler 인터페이스와 다르다 -> 이녀석의 invoke 메서드에서는 타깃오브젝트의 정보를 제공하지 않음
MethodInterceptor의 invoke 메서드 -> ProxyFactoryBean으로 부터 타깃오브젝트를 제공 받음. 따라서 타깃오브젝트에 구애 받지 않고 독릭접으로 구현 가능
그래서 여러 프록시에 함께 사용, 싱글톤 빈 등록 가능
invoke 메서드의 경우 ->
methodInvocation.proceed()는 Method와 달리타깃 정보를 함꼐 전달한다Advice 인터페이스를 상속하는 인터페이스
어드바이스 ! (타깃오브젝트가 필요없는 순수한 부가기능)
순수한 부가기능)MethodInvocation 같은 경우 타깃오브젝트와 함께 전달하여 실행하게 되며, 부가기능 구현에만 집중할 수 있도록 함 기 구현했던 InvocationHandler와의 차이점이다.
MethodInvocation은 일종의 콜백 오브젝트, procced 메서드 ->
타깃오브젝트의 메서드를 내부적으로 실행ProxyFactoryBean.addAdvice() -> 여러 개의 MethodInvocation을 추가할 수 있다는 장점
-> 즉, ProxyFactoryBran에 여러 가지 부가기능을 넣을 수 있음을 의미
타깃에 부가기능을 적용하는 것을
스프링에서는 어드바이스라고 한다순수한 기능을 담은 오브젝트라 할 수 있음
포인트 컷 ! 부가기능 적용대상의 메소드 선정
메소드 선정 알고리즘 ProxyFactoryBean -> Advice(부가기능) + PointCut(대상메서드) 관리 및 적용
NameMatchMethodPointcut (스프링 제공)
어드바이저 = advice + pointcut
DefaultPointcutAdvisor (스프링 제공)
어드바이스 따로, 포인트컷 따로 등록을 안하는 이유는 어떤 어드바이스에 어떤 포인트컷이 적용될지 애매해지기 때문이다.
따라서 어드바이저로 등록을 해야한다.
xml 설정
interceptorNames 속성 -> 어드바이스와 어드바이저를 혼합해서 설정 가능하다
스프링 AOP
투명성 : 분리를 통해 투명한 부가기능 형태로 제공 되어야함. 투명하다는 것은 마치 투명한 유리를 사이에 둔것 같다는 것. OCP개념이라고 볼 수 도 있다. 추가, 제거 및 확장에는 항상 열려 있어야 하다는 것. DI의 멋진 응용방식
빈후처리기
말 그대로 스프링 Bean 생성 후 후가공을 할 수 있는 라이프 사이클.
BeanPostProcessor 인터페이스
포인트컷의 2가지 기능
메소드 선정 -> MethodMatcher
오브젝트 선택 -> ClassFilter
DefaultAdvisorAutoProxyCreator
프록시 적용 대상이면, 프록시 오브젝트를 생성 -> 타겟오브젝트에 연결 -> 프록시를 통해서만 접근 가능 자동프록시 생성요 빈 후처리기
포인트컷 표현식을 이용한 포인트컷
AspectJExpressionPointcut 클래스 사용
AspectJ라는 외부 프레임워크를 확장하여 스프링에서 재 가공처리
AspectJ의 execution 지시자를 통해 설정
execution([접근제한자 패턴] 타입패턴 [타입패턴.] 이름패턴 (타입패턴|"..", ...))([throws 예외패턴])
와일드카드 : 그외 -
*, 파라티머 -..모든패키지, 클래스, 메서드이름, 예외이름, 리턴타입에 와일드카드 적용가능
클래스 :
..-> 서브패키지 / com..Target.* -> com으로 시작하는 패키지에서 Target클래스의 모든메서드필수 :
리턴타입, 메서드, 파라미터/ 나머지 생략가능포인트컷 표션식 -> 인터페이스, 추상클래스도 포인트컷 선정 가능
AspectJExpressionPointcut 사용시 아래 라이브러리 추가 (Maven)
Java -> setExpression으로 execution 설정 -> 포인트컷 클래스필터, 메소드매처 비교
Test
```java @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())", true, false, 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( .Target.(..))", true, true, true, true, true, false); targetClassPointcutMatches("execution( chap06_AOP.Target.(..))", true, true, true, true, true, false); targetClassPointcutMatches("execution( chap06_AOP...(..))", true, true, true, true, true, true); 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); }
private 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");
}
private void pointcutMatches(String expression, Boolean expected, Class<?> clazz, String methodName, Class<?>... args) throws Exception { AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut(); pointcut.setExpression(expression);
}
6.6 트랜잭션 속성
트랙잰션 매니저 :
PlatformTransactionManager트랜잭션 정의 :
DefaultTransactionDefinition
트랜잭션 정의
최소의 작업단위. TransactionDefinition 인터페이스 4가지 속성 정의
트랜잭션 전파 이미 진행 중인 트랜잭션이 있을 때 or 없을 때 어떻게 동작할 것인지 결정하는 방식 A트랜잭션 실행 중 B호출 -> B는 A의 트랜잭션 안에 있다. 이럴 경우 취소되면 둘다 롤백 B가 독립적인 트랜잭션을 갖고 있고, B는 끝난상태에서 A가 예외로 취소되면 A만 롤백
PROPAGATION_REQUIRED 속성 (가장많이 사용) : 있으면 그거쓰고, 없으면 새로만듬 -> 재사용
PROPAGATION_REQUIRED_NEW : 항상 새로운 트랜잭션 생성
PROPAGATION_NOT_SUPPORTED : 제외하고자 할 때 많이 쓰임
격리수준 서버에서는 여러개의 트랜잭션이 동시에 발생할 수 있다. 이것을 제어해줘야함
ISOLATION_DEFAULT,Datasoruce의 디폴트를 따르는 편이 좋지만, 때에 따라서는 독자적인 격리수준 필요제한시간 트랜잭션의 수행 제한시간 설정 DefaultTransactionDefinition 의
기본값은 제한시간 없음PROPAGATION_REQUIRED or PROPAGATION_REQUIRES_NEW 와 함께 사용해야만 의미 있음읽기 전용 데이터의 조작을 막음. 조회기능에 readonly 로 설정 하면 성능이 향상될수 있음
트랜잭션 인터셉터와 트랜잭션 속성
메소드별로 다른 트랜잭션 정의 -> 어드바이스 기능 확장
TransactionInterceptor 메소드 이름 패턴을 이용하여 다르게 지정 할 수 있음 런타임 예외 발생 -> 롤백 검사예외 발생 -> 커밋 1. PlatformTransactionManager 2. Properties
TransactionAttribute 정의 (트랜잭션 속성 4개 이용)
rollbackOn 메서드 : 어떤 예외 발생시 롤백할 것인가를 결정.
런타임-> 커밋 등 변경필요시 정의
tx네임스페이스 사용 tx스키마 전용 태그 -> 위의 트랜잭션 속성 명시 자동완성. 오타율 줄임
포인트컷과 트랜잭션 속성의 적용 전략
aop, tx 스카마의 전용태그 사용, 포인트컷의 execution 활용
트랜잭션 포인트컷 표현식 ->
타입패턴or빈이름이용 -> execution(타입패턴), bean(빈이름)가급적 타입패턴은 인터페이스타입 사용 권장
공통된 메서드 이름규칙 -> 최소한의 트랜잭션어드바이스와 속성 정의 하도록... 일반적인 경우가 아닐 때 -> 어드바이스와 포인트컷 새롭게 추가
tx:advice와 aop 이용한 방식
프록시 방식 AOP는 같은 타깃오브젝트 내의 메소드를 호출할 때는 적용 되지 않는다...
(주의사항)ex) 같은 타깃 내의 두메서드에 대해 delete()가
update() 호출하면update()에 적용 된 트랜잭션 관련 advice가 적용 되지 않고,delete()에 종속되어 적용이 된다. (delete는 클라이언트에서 호출, 실제로는 트랜잭션 Proxy가 타깃을 홀출할때 적용)ex) 위 예는 delete가 트랜잭션관련 어드바이스로 등록된 케이스지만, 만약에 트랜잭션 어드바이스가 없는 타깃메서드가
update를 호출 한다면 아에 트랜잭션 어드바이스가 적용조차 안되는 경우가 발생기본적인 읽기전요, REQUIRED 같은 속성에서는 문제 되지 않지만, 복잡한 트랜잭션 전파속성을 명시해야할 경우에는 각별한 주의가 필요하다!!
정리
스프링은 기본적으로 프록시기반의 AOP를 사용하지만(프록시사용시 문제점은 위에서 언급), 필요하다면 AspectJ 같은 바이트코드를 조작하는 범용적인 AOP기술을 사용 하면된다. 귀찮은 작업도 뒤따르긴 하지만 이만한 기술이 없다! 끝!
트랜잭션 속성 적용
비즈니스 로직을 담고 있는 서비스 계층 오브젝트의 메서드가 트랜잭션 경계를 부여하기에 가장 적절한 대상
부가로직 및 트랜잭션 속성을 분리하여 적용하기 위해서.. 서비스 -> DAO로 접근하도록 구현 (어떻게 보면 MVC패턴?) tx스키마에서 propagation 속성 생략 -> 디폴트 : REQUIRED
6.7 애노테이션 트랜잭션 속성과 포인트컷
설정파일로 적용하는 것 대신에 타겟에 트랙잰션 정보를 가진 애노테이션을 지정 할 수 있음
트랜잭션 애노테이션
@Transactional
타깃은
메서드와 타입-> 메서드, 클래스, 인터페이스에 적용 가능타겟오브젝트로 자동 인식함
트랜잭션 속성을 정의 + 포인트컷 자동등록
TransactionAttributeSourcePointcut사용
트랜잭션 속성을 이용하는 포인트컷
AnnotationTransactionAttributeSource 사용 -> 포인트컷 + 트랜잭션속성 지정
부가기능 적용 단위는 메서드 -> 동일한 트랜잭션 속성이 나타날 수 있다... ->
지저분해짐 단점
대체정책
위의 문제해결 -> 스프링에서 4단계 대체 정책
타깃메서드, 타깃클래스, 선언메서드, 선언타입(클래스, 이넡페이스) 순서에 따라 @Transactional이 적용 됐는지 차례로 확인 -> 가장먼저 발견되는 속성정보를 사용함
인터페이스에 @Transactional 사용을 권장하나. 스프링의 프록시방식이 아니라면 무시가 된다.. 이럴때는 구현클래스(타깃)에 명시해야한다. 그런데 이럴 경우 구현클래스마다 명시해야하는
중복성이 나타나게 되는 단점이 있기는하다.
트랜잭션 애노테이션을 사용을 위한 설정
어노테이션 사용은 xml 설정에 관련 사용여부 명시 필요
트랜잭션 어노테이션 적용
트랜잭션 없는 경우 DAO에서 예외를 발생시키기도 한다. JDBC는 -> 트랜잭션이 없어도 실행 됨
6.8 트랜잭션 지원 테스트
선언적 트랜잭션과 트랜잭션 전파 속성
A 메서드에서 시작된 트랜잭션.. B가 A에서 호출된다면 전파 속성에 따라 A는 B의 트랜잭션 단위의 일부로 통합이 된다. 이런 트랜잭션 경계설정과 전파에 의해 트랜잭션 관리가 이루어지게 됨
선언적 트랜잭션
AOP를 이용하여 코드 외부에서 트랜잭션 기능을 부여해주고, 속성을 지정할 수 있게 하는 방법
트랜잭션 동기화와 테스트
트랜잭션 추상화, AOP 등의 기술이 있었기에 가능 -> 선언적 트랜잭션, 트랜잭션 전파
트랜잭션의 추상화 핵심기술 1. 트랜잭션 매니저
PlatformTransactionManager ->
일관된 트랜잭션 제어,트랜잭션 참여트랜잭션 동기화
트랜잭션 전파가 가능한 이유
트랜잭션 정보를 저장해놓는
저장소 역할
테스트를 할 때 트랜잭션의 경계설정(시작)과 트랜잭션 전파 속성을 통해 롤백 및 커밋등 작업 유용
하이버네이트, JPA 같은 ORM등의 엔티티 테스트 할 때도 유용 / 여러 메서드를 조합해서 사용할 때도 유용
트랜잭션을 제어하여 테스트롤백 등 다양하게 응용을 할 수 있다!
-> 테스트 내의 모든 DB 작업을 하나의 트랜잭션 안에 두고
테스트가 끝나면 롤백을 해버리는 테스트!
테스트를 위한 트랜잭션 어노테이션
테스트에도 @Transactional을 적용 할 수 있음
@ContextConfiguration -> 스프링컨테이너를 초기화 해줌
@Transactional
테스트 클래스에 붙이면, 타깃 클래스나 인터페이스 적용 된 것처럼
테스트메서드에 트랜잭션 경계가 자동 설정됨테스트 내에 서 진행하는
모든 트랜잭션 관련 작업을 하나로 묶음!@Rollback
@Transactional 은 롤백을 담으려고 만든 어노테이션은 아니다. 따라서
롤백제어는 @Rollback을 통해 설정메소드 레벨에만 적용 가능
@TransactionConfiguration
(depreacted)클래스레벨에서 공통 속성 정의가능
트랜잭션 테스트와 비트랜잭션 테스트를 구분하여 클래스를 만들어서 테스트 수행하기를 권장
propagation=Propagation.NEVER ->
트랜잭션 시작안함
효과적인 DB 테스트
테스트는 서로 독립적으로 구현
DB 접근시에는 @Transactional을 통한 트랜잭션 설정과 롧백테스트 활용
코드가 바뀌지 않는 한 테스트 순서가 바껴도 일정한 결과를 낼 수 있도록 작성해야 한다
롤백테스트는 아주 유용하게 작용 될 것임!! 굿굿 특히 단위테스트에서
Last updated