일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
- Algorithm
- 카카오인턴
- Singtone
- 백준
- 알고리즘
- 카카오
- 토비의스프링
- 자바
- 스프링
- @Profile
- BinarySearch
- 전화번호 목록
- 스프링프로젝트 시작하기
- 플로이드워셜
- 이진탐색
- 스프링이란
- Java
- 플로이드와샬
- Spring이란
- 이진검색
- 11723
- 프로그래머스
- Spring
- 징검다리
- sope
- 그래프
- 구현
- 가장먼노드
- 쇠막대기 문제
- bitmasking
- Today
- Total
육감적 코딩
토비의스프링3.1 [6장] 6.5 스프링 AOP 본문
6.5 스프링 AOP
지금 까지 해왔던 작업의 목표는 트랜잭션 코드를 깔끔하고 효과적으로 분리해내는 것이다.
투명한 부가기능제공 |
마치 투명한 유리를 사이에 둔 것처럼 다른 코드에서는 그 존재가 보이지 않지만, 메소드가 호출되는 과정에 다이내믹하게 참여해서 부가적인 기능을 제공해주도록 만드는 것. |
6.5.1 자동 프록시 생성
투명한 부가기능을 적용하는 과정에서 발견됐던 거의 대부분의 문제는 제거했다.
하지만, 아직 해결할과제가 남아있다.
문제. 부가기능의 적용이 필요한 타깃 오브젝트마다 거의 비슷한 내용의 ProxyFactoryBean 빈 설정정보를 추가해 주는 부분.
예)
<bean id=”userService” class=”org.springframework.aop.framework.ProxyFactoryBean”>
<property name=”target” ref=”userServiceImpl”/>
<property name=”interceptorNames”>
<list>
<value>transactionAdvisor</value>
</list>
</property>
</bean>
<!-- 기존의 코드 ---->
<!-- 부가기능의 적용이 필요한 또 다른 타깃오브젝트 aService ---->
<bean id=”aService” class=”org.springframework.aop.framework.ProxyFactoryBean”>
<property name=”target” ref=”aServiceImpl”/>
<property name=”interceptorNames”>
<list>
<value>transactionAdvisor</value>
</list>
</property>
</bean>
<! -- 많은 중복 발생 ---->
빈 후처리기를 이용한 자동 프록시 생성기
관심을 가질 만한 확장 포인트는 BeanPostProcessor 인터페이스를 구현해서 만드는 빈 후처리기다.
DefaultAdvisorAutoProxyCreator는 어드바이저를 이용한 자동 프록시 생성기다.
빈 후처리기를 이용한 자동 프록시 생성 방법
-
DefaultAdvisorAutoProxyCreator 빈 후처리기가 등록되어 있으면 스프링은 빈 오브젝트를 만들 때마다 후처리기에 빈을 보낸다.
-
DefaultAdvisorAutoProxyCreator는 빈으로 등록된 모든 어드바이저 내의 포인트컷을 확인하여 적용 대상인지 확인.
-
적용 대상이면 내장된 프록시 생성기에게 현재 빈에대한 프록시를 만들게 한다.
-
만들어진 프록시에 어드바이저를 연결해준다.
-
빈 후처리기는 프록시가 생성되면 원래 컨테이너가 전달해준 빈 오브젝트 대신 프록시 오브젝트를 컨테이너에게 돌려준다.
확장된 포인트컷
사실 포인트 컷은 두 가지 기능을 가지고 있다.
public interface Pointcut{
ClassFilter getClassFilter(); //프록시를 적용할 클래스인지 확인해 준다.
MethodMatcher getMethodMatcher(); // 어드바이스를 적용할 메소드인지 확인해준다.
}
포인트 컷은 클래스 필터와 메소드 매처 두 가지를 돌려주는 메소드를 갖고 있다.
실제 포인트컷의 선별 로직은 이 두가지 타입의 오브젝트에 담겨 있다.
-
기존의 NamedMatchMethodPointcut은 메소드 선별 기능만 가진 포인트컷이다.
-
메소드만 선별하기 때문에 클래스의 종류는 상관없이 메소드만 판별했다.
-
why? 기존의 코드에서는 타깃이 이미 정해져 있기 때문
빈 후처리기인 DefaultAdvisorAutoProxyCreator는 이런 포인트컷과 어드바이스를 결합한 어드바이저가 등록되어 있어야 한다.
6.5.2 DefaultAdvisorAutoProxyCreator의 적용
클래스 필터를 적용한 포인트컷 작성
NameMatchMethodPointcut을 상속해서 프로퍼티로 주어진 이름 패턴을 가지고 클래스 이름을 비교하는 ClassFilter를 추가하도록 만든다.
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){
// * 가 들어간 문자열 비교를 지원하는 스프링의 유틸리티 메소드.
// *name , name* , *name* 세 가지 방식을 모두 지원.
return PatternMatchUtils.simpleMatch(mappedName, clazz.getSimpleName());
}
}
}
어드바이저를 이용하는 자동 프록시 생성기 등록
DefaultAdvisorAutoProxyCreator는 등록된 빈 중에서 Advisor 인터페이스를 구현한 것을 모두 찾는다. 그리고 적용 가능한 빈에 대해 어드바이저 포인트컷을 적용하면서 프록시 적용 대상을 선정한여 원래 빈 오브젝트와 바꿔치기 한다. 따라서 타깃 빈에 의존한다고 정의한 다른 빈들은 프록시 오브젝트를 대신 DI 받게 된다.
<bean class = “org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreaotr” />
<!-- 다른 빈에서 참조되거나 코드에서 빈 이름으로 조회될 필요가 없는 빈이라면 아이디 등록을 하지 않아도 된다 --->
포인트컷 등록
기존의 포인트컷 설정을 삭제하고 새로 만들 클래스 필터 지원 포인트컷을 빈으로 등록한다.
<bean id=”transactionPointcut” class=”springbook.service.NameMatchClassMethodPointcut”>
<property name=”mappedClassName” value=”*ServiceImpl” /> <!-- 클래스 이름패턴--->
<property name=”mappedName” value=”upgrade*” /> <!-- 메소드 이름패턴 --->
</bean>
어드바이스와 어드바이저
어드바이스인 transactionAdvice 빈의 설정은 수정할게 없다. 어드바이저인 transactionAdvisor 빈도 수정할 필요는 없다. 하지만 어드바이저로 사용되는 방법이 바뀌었다는 사실은 기억해두자.
→ 기존: ProxyFactoryBean으로 등록한 빈에서 transactionAdvisor를 명시적으로 DI함.
→ 현재: DefaultAdvisorAutoProxyCreator에 의해 어드바이저가 자동 수집되고, 프록시 대상 선정 과정에 참여하여, 자동생성된 프록시에 다이내믹하게 DI 돼서 동작하는 어드바이저가 됨.
ProxyFactoryBean 제거와 서비스 빈의 원상복구
더 이상 명시적인 프록시 팩토리 빈을 등록하지 않기 때문에, userServiceImpl 빈의 아이디를 다시 userService로 돌려놓을 수 있다.
자동 프록시 생성기를 사용하는 테스트
기존에 만들어서 사용하던 강제 예외 발생용 TestUserService 클래스를 이제는 직접 빈으로 등록해보자.
문제점1. TestUserService가 UserServiceTest클래스의 내부에 정의된 스태틱 클래스라는 점.
문제점2. 포인트컷의 대상 클래스의 이름 패턴이 *ServiceImpl이라도 되어있어 TestUserService 클래스는 빈으로 등록을 해도 포인트컷이 적용대상으로 선정하지 않는 점.
문제점1 → 스태틱 클래스 자체는 스프링의 빈으로 등록하는 데 문제 X
<bean id=”testUserService” class=”springbook.user.service.UserServiceTest$TestUserServiceImpl” parent=”userService” />
-
스태틱 멤버 클래스는$로 지정한다.
-
parent ~ : 프로퍼티 정의를 포함해서 UserService빈의 설정을 상속받는다.
문제점2 → TestUserService의 클래스 이름을 TestUserServiceImpl로 변겅.
static class TestUserServiceImpl extends UserServiceImpl {
private String id = “madnite1”; // 테스트 픽스처의 user(3)의 id값을 고정
protected void upgradeLevel(User user){
if(user.getId().equals(this.id)) trow new TestUserServiceException();
super.upgradeLevel(user);
}
}
마지막으로 upgradeAllOrNothing() 테스트를 새로 추가한 testUserService 빈을 사용하도록 수정.
testUserService 빈도 UserService 타입이므로 @Autowired로 가져오면 된다 .단 타입이 중복 되므로 타입뿐 아니라 변수 이름을 빈 이름과 일치시켜줘야한다.
public class UserServiceTest{
@Autowired UserService userService;
@Autowired UserService testUserService;
…
public void upgradeAllOrNothing(){
userDao.deleteAll();
for(User user: users) userDao.add(user);
try{
this.testUserService.upgradeLevels();
fail(“TestUserServiceException expected”);
}
catch(TestUserServiceException e){}
checkLevelUpgraded(user.get(1), false);
}
}
자동생성 프록시 확인
DefaultAdvisorAutoProxyCreator에 의해 userService 빈이 프록시로 바꿔치기됐다면 getBean(“userService”)로 가져온 오브젝트는 TestUserService 타입이 아니라 JDK의 Proxy타입일 것이다. 모든 JDK 다이내믹 프록시 방식으로 만들어지는 프록시는 Proxy 클래스의 서브클래스이기 때문이다.
@Test
public void advisorAutoProxyCreator(){
assertThat(testUserService, is(java.lang.reflect.Proxy.class));
}
6.5.3 포인트컷 표현식을 이용한 포인트컷
포인트컷 표현식
AspectJExpressionPointcut은 클래스와 메소드의 선정 알고리즘을 포인트컷 표현식을 이용해 한 번에 지정할 수 있게 해준다. AspectJ의 일부 문법을 확장해서 사용하는것이다. 그래서 이를 AspectJ 포인트컷 표현식이라고도 한다.
포인트컷 표현식 문법
execution([접근제한자 패턴] 타입패턴 [타입패턴.]이름패턴 (타입패턴 | ”..”, ...) [throws 예외패턴]) |
● [접근제한자 패턴] : public, private 등 (생략가능)
● 타입패턴 : 리턴 값의 타입 패턴
● [타입패턴.]: 패키지와 클래스 이름에 대한 패턴. 생략가능
● 이름패턴 : 메소드 이름 패턴
● (타입패턴 | ”..”, ...) : 파라미터의 타입 패턴을 순서대로 넣을 수 있다. 와일드카드를 이용해 파라미터 개수에 상관없는 패턴을 만들 수 있다.
● [throws 예외 패턴] : 예외 이름 패턴
execution(* minus(int,int )) |
리턴 타입은 상관없이 minus라는 메소드 이름, int파라미터를 두개 가진 모든 메소드를 선정하는 포인트컷 표현식 |
execution(* minus(..)) |
리턴 타입과 파라미터의 종류, 개수 상관없이 minus라는 메소드이름을 가진 모든 메소드를 선정하는 포인트컷 표현식 |
execution(* *(..)) |
리턴 타입, 파라미터, 메소드 이름에 상관없이 모든 메소드 조건을 다 허용하는 포인트컷 표현식 |
포인트컷 표현식을 이용하는 포인트컷 적용
execution() 외에도 몇 가지 표현식 스타일이 있다.
-
bean() : bean(*Service) 라고 쓰면 아이디가 Service로 끝나는 모든 빈을 선택한다. ( 클래스와 메소드라는 기준X)
-
@annotation() : 특정 애노테이션이 적용된 메소드를 선정할 수 있다.
기존 |
<property name=”mappedClassName” value=”*ServiceImpl” /> |
포인트컷 표현식을 이용한 빈 설정 |
<bean id=”transactionPointcut” class=”org.springframework.aop.aspectj.AspectJExpressionPointcut”> |
타입 패턴과 클래스 이름 패턴
기존의 TestUserService 였던 테스트용 UserService 구현 클래스의 이름을 이름 패턴에 맞추려고 TetstUserServiceImpl로 변경하였다.
이를 다시 TetsUserService로 바꾸어도 execution(* *..*ServiceImpl.upgrade*(..)) 로 되어있는 포인트컷의 선정대상에 포함된다.
why?
포인트컷 표현식의 클래스 이름에 적용되는 패턴은 클래스 이름 패턴이 아니라 타입 패턴이기 때문이다.
TestUserService의 클래스 이름은 TestUserService 이지만, 타입을 따져보면 TestUserService 클래스 이고, 슈퍼클래스인 UserServiceImpl, 구현 인터페이스인 UserService 세 가지가 모두 적용된다.
6.5.4 AOP란 무엇인가?
UserService에 트랜잭션을 적용해온 과정 정리.
트랜잭션 서비스 추상화
트랜잭션 경계설정 코드를 비즈니스 로직을 담은 코드에 넣으면서 맞닥뜨린 첫 번째 문제는 특정 트랜잭션 기술에 종속되는 코드가 돼버린다는 것이었다.
그래서 트랜잭션 적용이라는 추상적인 작업 내용은 유지한 채로 구체적인 구현 방법을 자유롭게 바꿀 수 있도록 서비스 추상화 기법을 적용했다.
결국 DI를 통해 무엇을 하는지는 남기고, 그것을 어떻게 하는지를 분리한 것이다.
프록시와 데코레이터 패턴
여전히 비즈니스 로직 코드에는 트랜잭션을 적용하고 있다. 그래서 DI를 이용한 데코레이터 패턴을 적용했다. 클라이언트가 인터페이스와 DI를 통해 접근하도록 설계하였다. 트랜잭션을 처리하는 코드는 일종의 데코레이터에 담겨서, 클라이언트와 비즈니스로직을 담은 타깃 클래스 사이에 존재하도록 만들었다. 그래서 클라이언트가 프록시 역할을 하는 트랜잭션 데코레이터를 거쳐서 타깃에 접근할 수 있게 됐다.
다이내믹 프록시와 프록시 팩토리 빈
비즈니스 로직 인터페이스의 모든 메소드마다 트랜잭션 기능을 부여하는 코드를 넣어 프록시 클래스를 만드는 작업이 오히려 불편한 점이 됐다. 그래서 프록시 클래스 없이도 프록시 오브젝트를 런타임 시에 만들어주는 JDK 다이내믹 프록시 기술을 적용했다.
하지만 동일한 기능의 프록시를 여러 오브젝트에 적용할 경우 오브젝트 단위로는 중복이 일어나는 문제는 해결하지 못했다.
스프링의 프록시 팩토리 빈을 이용해서 다이내믹 프록시 생성 방법에 DI를 도입했다.
자동 프록시 생성 방법과 포인트컷
트랜잭션 적용대상이 되는 빈마다 일일이 프록시 팩토리 빈을 설정해줘야 한다는 부담이 남아있다. 이를 해결하기 위해서 스프링 컨테이너의 빈 생성 후처리 기법을 활용해 컨테이너 초기화 시점에서 자동으로 프록시를 만들어주는 방법을 도입했다.
부가기능의 모듈화
트랜잭션적용 코드는 기존에 써왔던 방법으로는 간단하게 분리해서 독립된 모듈로 만들 수가 없다.
부가기능이기 때문에 독립적인 방식으로 적용되기 어렵기 때문이다. 그래서 핵심기능의 곳곳에서 나타나는 부가기능을 어떻게 독립적인 모듈로 만들 수 있을까 고민했고,
위에서 나온 개념들이 이런 문제를 해결하기 위해 적용한 대표적인 방법이다.
결국 지금까지 해온 모든 작업은 핵심기능에 부여되는 부가기능을 효과적으로 모듈화 하는 방법을 찾는 것이었고, 어드바이스와 포인트컷을 결합한 어드바이저가 단순하지만 이런 특성을 가진 모듈의 원시적인 형태로 만들어지게 됐다.
AOP: 애스펙트 지향 프로그래밍
애스펙트는 부가될 기능을 정의한 코드인 어드바이스와, 어디에 적용할지를 결정하는 포인트컷을 함께 갖고 있다.
애스펙트는 단어 그대로 애플리케이션을 구성하는 한 가지 측면이라고 생각 할 수 있다.
기존의 코드는 핵심기능과 부가기능이 같이 들어있기에 코드가 복잡하고 지저분했지만,
이를 입체적구조로 가져가면서 각각 성격이 다른 부가기능은 다른면에 존재하도록 만들었다.
이렇게 핵심적인 기능에서 부가적인 기능을 분리해서 애스펙트라는 독특한 모듈로 만들어서 설계하고 개발하는 방법을 AOP 라고 부른다.
마치 OOP(객체지향프로그래밍)의 다른 개념같이 느껴지지만, AOP는 OOP를 돕는 보조적인 기술이지 OOP를 대체하는 개념은 아니다. AOP는 애스펙트를 분리하여, 핵심기능을 설계하고 구현할 때 객체지향적인 가치를 지킬 수 있도록 도와주는 것이라고 보면 된다.
6.5.6 AOP의 용어
타깃
부가기능을 부여할 대상.
어드바이스
타깃에게 제공할 부가기능을 담은 모듈.
조인 포인트
어드바이스가 적용될 수 있는 위치를 말한다. 스프링의 AOP에서 조인포인트는 메소드의 실행 단계 뿐이다. 타깃 오브젝트가 구현한 인터페이스의 모든 메소드는 조인 포인트가 된다.
참고: http://credemol.blogspot.com/2010/04/aop.html
조인포인트는 애플리케이션 실행 중의 특정한 지점을 의미한다. 전형적인 조인포인트의 예로는 메서드 호출, 메서드 실행 자체, 클래스 초기화, 객체 생성시점 등이 있다. 조인포인트는 AOP 핵심 개념이며 애플리케이션의 어떤 지점에 AOP를 사용하여 추가적인 로직을 삽입할 지 정의한다.
포인트컷
어드바이스를 적용할 조인 포인트를 선별하는 작업 또는 그 기능을 정의한 모듈을 말한다.
프록시
클라이언트와 타깃 사이에 존재하면서 부가기능을 제공하는 오브젝트다. DI를 통해 타깃 대신 클라이언트에게 주입되며, 클라이언트의 메소드 호출을 대신 받아서 타깃에 위임해주면서, 그 과정에서 부가기능을 부여한다.
어드바이저
포인트컷과 어드바이스를 하나씩 갖고 있는 오브젝트다.
스프링 AOP에서만 사용되는 특별한 용어이고, 일반적인 AOP에서는 사용되지 않는다.
애스펙트
AOP의 기본 모듈. 한 개 또는 그 이상의 포인트컷과 어드바이스의 조합으로 만들어지며 보통 싱글톤 형태의 오브젝트로 존재한다.
6.5.7 AOP 네임스페이스
스프링의 프록시 방식AOP를 적용하려면 최소한 네 가지 빈을 등록해야한다.
자동 프록시 생성기
DefaultAdvisorAutoProxyCreator 클래스를 빈으로 등록한다. 다른 빈을 DI하지도 않고 DI되지도 않기 때문에 id도 굳이 필요없다. 빈 오브젝트를 생성하는 과정에 빈 후처리기로 참여한다.
어드바이스
부가기능을 구현한 클래스를 빈으로 등록한다.
포인트컷
스프링의 AspectJExpressionPointcu을 빈으로 등록하고 expression 프로퍼티에 포인트컷 표현식을 넣어주면 된다.
어드바이저
DefaultPointcutAdvisor 클래스를 빈으로 등록해서 사용한다. 어드바이스와 포인트컷을 프로퍼티로 참조하는 것 외에는 기능은 없다. 자동프록시 생성기에 의해 자동 검색되어 사용된다.
AOP 네이스페이스
aop 네임스페이스를 선언하면
<aop:config> |
AOP설정 빈을 간단히 바꿀 수 있다.
'정리 > Spring' 카테고리의 다른 글
토비의스프링3.1 [7장] 7.5 DI를 이용해 다양한 구현 방법 적용하기 (0) | 2020.07.29 |
---|---|
[Spring] 3. @Autowired (0) | 2020.07.23 |
[Spring] 2. ApplicationContext와 다양한 빈 설정 방법 (0) | 2020.07.22 |
[Spring] 1. IoC컨테이너와 빈 (0) | 2020.07.20 |
토비의스프링3.1 [2장] 테스트 (0) | 2020.06.19 |