-
6장) 6.4 스프링의 프록시 팩토리 빈Java & Spring/토비의 스프링 3.1 2021. 7. 16. 18:22반응형
6장 AOP
6.4 스프링의 프록시 팩토리 빈
- ProxyFactoryBean
- 자바 JDK에서 제공하는 다이나믹 프록시 외에도 프록시를 만들도록 지원하는 다양한 기술 존재
- 스프링은 일관된 방법으로 프록시를 만들 수 있게 도와주는 추상 레이어 제공
- 프록시 오브젝트를 생성해주는 기술을 추상화한 팩토리 빈 제공
- 스프링은 일관된 방법으로 프록시를 만들 수 있게 도와주는 추상 레이어 제공
- 프록시를 생성하는 작업만 담당
- 부가기능은 MethodInterceptor 인터페이스를 구현해서 사용
- InvocationHandler의 invoke() 는 타깃 오브젝트 정보를 제공하지 않아서, 자체적으로 타깃을 알고 있어야함
- MethodInterceptor의 invoke()는 ProxyFactoryBean으로 부터 타깃 오브젝트 정보를 제공받음
- 타깃 오브젝트에 상관없이 독립적으로 만들어 질 수 있고, 다른 여러 프록시에서 함께 사용 가능하며, 싱글톤 빈으로 등록 가능
-
public class DynamicProxyTest { @Test public void SimpleProxy() { Hello proxiedHello = (Hello)Proxy.newProxyInstance( getClass().getClassLoader(), new Class[] { Hello.class }, new UppercaseHandler(new HelloTarget()) ); ... } @Test public void proxyFactoryBean() { ProxyFactoryBean pfBean = new ProxyFactoryBean(); pfBean.setTarget(new HelloTarget()); pfBean.addAdvice(new UppercaseAdvice()); Hello proxiedHello = (Hello) pfBean.getObject(); assertThat(proxiedHello.sayHello("Toby"), is("HELLO TOBY")); assertThat(proxiedHello.sayHi("Toby"), is("Hi TOBY")); } static class UppercaseAdvice implements MethodInterceptor { public Object invoke(MethodInterceptor invocation) throws Throwable { String ret = (String)invocation.proceed(); return ret.toUpperCase(); } } static interface Hello { String sayHello(String name); String sayHi(String name); } static class HelloTarget implements Hello { public String sayHello(String name) { return "Hello " + name; } public String sayHi(String name) { return "Hi " + name; } } }
- 부가기능은 MethodInterceptor 인터페이스를 구현해서 사용
- 어드바이스: 타깃이 필요 없는 순수한 부가기능
- MethodInvocation은 일종의 콜백 오브젝트로, proceed() 메소드를 실행해서 타깃 오브젝트 메소드를 내부적으로 실행
- 프록시 추상화 기능인 ProxyFactoryBean을 사용하는 코드의 가장 큰 차이점이자 장점
- ProxyFactoryBean은 작은 단위의 템플릿/콜백 구조를 응용해서 적용
- 템플릿 역할인 MethodInvocation을 싱글톤으로 두고 공유 가능
- MethodInterceptor는 Advice 인터페이스를 상속하고있는 서브 인터페이스
- 따라서, ProxyFactoryBean의 addAdvice()로 여러 MethodInterceptor 추가 가능
- ProxyFactoryBean 하나만으로 여러 부가 기능을 제공하는 프록시를 만들 수 있음
- 부가기능을 담은 오브젝트를 스프링에서는 Advice라고 부름
- 따라서, ProxyFactoryBean의 addAdvice()로 여러 MethodInterceptor 추가 가능
- ProxyFactoryBean은 인터페이스 자동검출 기능을 사용해 타깃 오브젝트가 구현하고 있는 인터페이스 정보를 알아내서, 인터페이스를 모두 구현하는 프록시를 만들어 반환
- 인터페이스 중 일부만 프록시에 적용하기 원한다면, setInterfaces()를 통해 구현할 인터페이스 정보를 직접 제공
- MethodInvocation은 일종의 콜백 오브젝트로, proceed() 메소드를 실행해서 타깃 오브젝트 메소드를 내부적으로 실행
- 포인트컷: 부가기능 적용 대상 메소드 선정 방법
- ProxyFactoryBean과 MethodInterceptor를 사용하는 방식에 메소드 선정 기능을 추가할 수 있을까???
- 트랜잭션 적용 메소드 패턴은 프록시마다 다를 수 있기 때문에, 여러 프록시가 공유하는 MethodInterceptor에 특정 프록시에만 적용되는 패턴을 넣으면 문제 발생
- 프록시에 부가기능 적용 메소드를 선택하는 기능을 넣어 해결 가능
- 프록시의 핵심 가치는 타깃을 대신해서 클라이언트 요청을 받아 처리하는 오브젝트 존재 자체로, 메소드 선별 기능은 프록시로부터 다시 분리
- 전략 패턴 적용
- 부가기능(Advice)와 메소드 선정 알고리즘(Pointcut)을 활용한 유연한 구조
- 포인트컷 : 메소드 선정 알고리즘 오브젝트
어드바이스 : 부가기능 제공 오브젝트 - 타깃 메소드를 직접 호출하는 것은 Invocation 콜백의 역할로, 전형적인 템플릿/콜백 구조
- 클라이언트 요청 ~> 포인트컷에게 부가기능 부여할 메소드인지 확인 요청
- 포인트컷 응답
- 부가기능 부여할 메소드인 경우
- MethodInterceptor 어드바이스 호출
- 기능 추가
- 부가기능 부여할 메소드인 경우
- invocation 콜백을 통해 타깃 오브젝트 메소드 실행
- 전략 패턴 구조
- 프록시로부터 어드바이스와 포인트 컷을 독립시키고 DI를 사용
- 여러 프록시가 공유, 부가기능 방식이나 메소드 선정 알고리즘이 바뀌면 구현 클래스만 바꿔 설정 ~> 자유로운 확장
- 포인트컷 : 메소드 선정 알고리즘 오브젝트
- 학습 테스트
-
@Test public void pointAdvisor() { ProxyFactoryBean pfBean = new ProxyFactoryBean(); pfBean.setTarget(new HelloTarget()); NameMatchMethodPointCut pointcut = new NameMatchMethodPointCut(); pointcut.setMappedName("sayH*"); pfBean.addAdvisor(new DefaultPointcutAdvisor(pointcut, new UppercaseAdvice())); Hello proxiedHello = (Hello)pfBean.getObject(); //assertThat .. }
- ProxyFactoryBean에는 여러 어드바이스와 포인트 컷이 추가될 수 있기 때문에, 어떤 어드바이스에 어떤 포인트컷을 적용할지 조합을 만들어서 등록
어드바이저 = 포인트컷 + 어드바이스
-
- ProxyFactoryBean과 MethodInterceptor를 사용하는 방식에 메소드 선정 기능을 추가할 수 있을까???
- 자바 JDK에서 제공하는 다이나믹 프록시 외에도 프록시를 만들도록 지원하는 다양한 기술 존재
- ProxyFactoryBean 적용
- 다이나믹 프록시를 이용해 만든 TxProxyFactoryBean을 스프링의 ProxyFactoryBean을 이용하도록 수정
- TransactionAdvice
- 부가기능을 담당하는 어드바이스를 MethodInterceptor라는 Advice 서브인터페이스를 구현해서 작성
- JDK 다이나믹 프록시 방식으로 만든 TransactionHandler의 코드에서 타깃과 메서드 선정 부분을 제거
-
public class TransactionAdvice implements MethodInterceptor { PlatformTransactionManager transactionManager; public void setTransactionManager(PlatformTransactionManager transactionManager) { this.transactionManager = transactionManager; } /* * 타깃을 호출하는 기능을 가진 콜백 오브젝트를 프록시로부터 받는다. * 덕분에 어드바이스는 특정 타깃에 의존하지 않고 재사용이 가능하다. */ @Override public Object invoke(MethodInvocation invocation) throws Throwable { TransactionStatus status = this.transactionManager.getTransaction(new DefaultTransactionDefinition()); try { /* * 콜백을 호출해서 타깃의 메서드를 실행 * 타깃 메서드 호출 전후로 필요한 부가기능을 넣을 수 있다. * 겨웅에 따라서 타깃이 아예 호출되지 않게 하거나 재시도를 위한 반복적인 호출도 가능하다. */ Object ret = invocation.proceed(); this.transactionManager.commit(status); return ret; /* * JDK 다이나믹 프록시가 제공하는 Method와는 달리 * 스프링의 MethodInvation을 통한 타깃 호출은 예외가 포장되지 않고 * 타깃에서 보낸 그대로 전달된다. */ } catch (RuntimeException e) { this.transactionManager.rollback(status); throw e; } } }
- 스프링의 XML 설정 파일
- 트랜잭션 어드바이스 및 포인트컷을 설정해주고, 포인트컷과 어드바이스를 담는 어드바이저 빈 설정을 해준다.
- 어드바이저 빈 설정을 마치면 ProxyFactoryBean 설정에 어드바이저 빈을 지정
- interceptorNames라는 propery에 등록
- advisor가 아닌 이유는 어드바이스와 어드바이즈러를 혼합해서 설정할 수 있도록 하기 위함
- 테스트
-
@Test @DirtiesContext public void upgradeAllOrNothing() { TestUserService testUserService = new TestUserService(users.get(3).getId()); testUserService.setUserDao(userDao); testUserService.setMailSender(mailSender); ProxyFactoryBean txProxyFactoryBean = context.getBean("&userService", ProxyFactoryBean.class); txProxyFactoryBean.setTarget(testUserService); UserService txUserService = (UserService)txProxyFactoryBean.getObject(); }
-
- 어드바이스와 포인트컷의 재사용
- ProxyFactoryBean은 스프링의 DI, 템플릿/콜백 패턴, 서비스 추상화 등 기법이 모두 적용됨
- 독립적이며 여러 프록시가 공유할 수 있는 어드바이스와 포인트컷으로 확장 기능을 분리 가능
- 메소드 선정을 위한 포인트컷이 필요하면 이름 패턴만 지정해서 ProxyFactoryBean에 등록
- ProxyFactoryBean은 스프링의 DI, 템플릿/콜백 패턴, 서비스 추상화 등 기법이 모두 적용됨
반응형'Java & Spring > 토비의 스프링 3.1' 카테고리의 다른 글
6장) 6.6 트랜잭션 속성 (0) 2021.07.26 6장) 6.5 스프링 AOP (0) 2021.07.23 6장) 6.3 다이내믹 프록시와 팩토리 빈 (0) 2021.07.16 6장) 6.1 트랜잭션 코드의 분리 ~ 6.2 고립된 단위 테스트 (0) 2021.07.11 5장) 5.3 서비스 추상화와 단일 책임 원칙 ~ 5.5 정리 (0) 2021.07.02 - ProxyFactoryBean