430 likes | 661 Views
고급자바프로그래밍 (Advanced Java Programming). 강원대학교 컴퓨터학부 2012 년 가을학기 담당교수 정충교. 6 장 AOP 1. 6.1 트랙스액션 코드의 분리. interface UserService { void add(User user); void upgradeLevels (); }. UserServiceTx. public void upgradeLevels () { TransactionStatus status = this.transactionManager
E N D
고급자바프로그래밍(Advanced Java Programming) 강원대학교컴퓨터학부 2012년 가을학기 담당교수정충교
6.1 트랙스액션 코드의 분리 interface UserService { void add(User user); void upgradeLevels(); }
UserServiceTx 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; } }
UserService publicvoidupgradeLevels() { List<User> users = userDao.getAll(); for(User user : users) { if(canUpgradeLevel(user)) { upgradeLevel(user); } } }
<beanid="userService"class="springbook.user.service.UserServiceTx"><beanid="userService"class="springbook.user.service.UserServiceTx"> <propertyname="transactionManager"ref="transactionManager"/> <propertyname="userService"ref="userServiceImpl"/> </bean> <beanid="userServiceImpl"class="springbook.user.service.UserServiceImpl"> <propertyname="userDao"ref="userDao"/> <propertyname="mailSender"ref="mailSender"/> </bean>
static class MockUserDao implements UserDao { private List<User> users; private List<User> updated = new ArrayList(); private MockUserDao(List<User> users) { this.users = users; } public List<User> getUpdated() { return this.updated; } public List<User> getAll() { return this.users; } public void update(User user) { updated.add(user); } public void add(User user) { throw new UnsupportedOperationException(); } public void deleteAll() { throw new UnsupportedOperationException(); } public User get(String id) { throw new UnsupportedOperationException(); } public intgetCount() { throw new UnsupportedOperationException(); } }
프록시와타겟 타겟의 대리인 역할
데코레이션 패턴 런타임에 동적으로 부가기능 삽입
프록시 패턴 프록시가타겟에 대한 접근 방법을 제어할 때 • 타겟 오브젝트 생성 시점 지연 • 원격 오브젝트 이용 • 타겟에 대한 접근 권한 제한
간단한 프록시 예 static class HelloTarget implements Hello { public String sayHello(String name) { return "Hello " + name; } public String sayHi(String name) { return "Hi " + name; } public String sayThankYou(String name) { return "Thank You " + name; } } interface Hello { String sayHello(String name); String sayHi(String name); String sayThankYou(String name); }
class HelloUppercase implements Hello { Hello hello; public HelloUppercase(Hello hello) { this.hello = hello; } public String sayHello(String name) { return hello.sayHello(name).toUpperCase(); } public String sayHi(String name) { return hello.sayHi(name).toUpperCase(); } public String sayThankYou(String name) { return hello.sayThankYou(name).toUpperCase(); } } interface Hello { String sayHello(String name); String sayHi(String name); String sayThankYou(String name); }
interface Hello { String sayHello(String name); String sayHi(String name); String sayThankYou(String name); } @Test public void simpleProxy() { Hello hello = new HelloTarget(); assertThat(hello.sayHello("Toby"), is("Hello Toby")); assertThat(hello.sayHi("Toby"), is("Hi Toby")); assertThat(hello.sayThankYou("Toby"), is("Thank You Toby")); Hello proxiedHello = new HelloUppercase(new HelloTarget()); assertThat(proxiedHello.sayHello("Toby"), is("HELLO TOBY")); assertThat(proxiedHello.sayHi("Toby"), is("HI TOBY")); assertThat(proxiedHello.sayThankYou("Toby"), is("THANK YOU TOBY")); }
class HelloUppercase implements Hello { Hello hello; public HelloUppercase(Hello hello) { this.hello = hello; } public String sayHello(String name) { return hello.sayHello(name).toUpperCase(); } public String sayHi(String name) { return hello.sayHi(name).toUpperCase(); } public String sayThankYou(String name) { return hello.sayThankYou(name).toUpperCase(); } } 인터페이스의 모든 메소드를 구현해야 함 똑같은 부가 기능이 모든 메소드에 중복됨
리플렉션(Reflection) • 자바의 모든 클래스는 자체 구성 정보를 담은 Class 타입 오브젝트를 하나씩 가지고 있다. • 클래스이름.class • getClass() – 클래스의 오브젝트를 가지고 있는 경우 • 클래스 이름, 슈퍼클래스, 인터페이스, 필드이름과 타입, 메소드 이름과파라미터와리턴타입, 오브젝트의 필드 값, 메소드 호출
public class Reflection { @Test public void invokeMethod() throws Exception { String name = "Spring"; // length assertThat(name.length(), is(6)); Method lengthMethod = String.class.getMethod("length"); assertThat((Integer)lengthMethod.invoke(name), is(6)); // toUpperCase assertThat(name.charAt(0), is('S')); Method charAtMethod = String.class.getMethod("charAt", int.class); assertThat((Character)charAtMethod.invoke(name, 0), is('S')); } @Test public void createObject() throws Exception { Date now = (Date) Class.forName("java.util.Date").newInstance(); } }
리플렉션을 이용한 다이내믹 프록시 • 다이내믹 프록시는 런타임에 프록시팩토리에 의해 생성 • 다이내믹 프록시 오브젝트는 타킷의 인터페이스 타입 • 클라이언트는 타겟 인터페이스를 통해 다이내믹프록시 사용
class UppercaseHandler implements InvocationHandler{ Object target; private UppercaseHandler(Object target) { this.target = target; } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object ret = method.invoke(target, args); if (ret instanceof String && method.getName().startsWith("say")) { return ((String)ret).toUpperCase(); } else { return ret; } } }
class UppercaseHandler implements InvocationHandler{ Object target; private UppercaseHandler(Object target) { this.target = target; } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object ret = method.invoke(target, args); if (ret instanceof String && method.getName().startsWith("say")) { return ((String)ret).toUpperCase(); } else { return ret; } } } Hello proxiedHello = (Hello)Proxy.newProxyInstance( getClass().getClassLoader(), new Class[] { Hello.class}, new UppercaseHandler(new HelloTarget()));
class UppercaseHandler implements InvocationHandler { Object target; private UppercaseHandler(Object target) { this.target = target; } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object ret = method.invoke(target, args); if (ret instanceof String && method.getName().startsWith("say")) { return ((String)ret).toUpperCase(); } else { return ret; } } } Hello proxiedHello = (Hello)Proxy.newProxyInstance( getClass().getClassLoader(), new Class[] { Hello.class}, new UppercaseHandler(new HelloTarget()));
다이내믹 프록시를 이용한 트랜랙색션부가기능 인터페이스의 모든 메소드를 구현해야 함 똑같은 부가 기능이 모든 메소드에 중복됨
public class TransactionHandlerimplements InvocationHandler{ 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 faultTransactionDefinition()); try { Object ret = method.invoke(target, args); this.transactionManager.commit(status); return ret; } catch (InvocationTargetException e) { this.transactionManager.rollback(status); throw e.getTargetException(); } } }
@Test public void upgradeAllOrNothing() throws Exception { TransactionHandlertxHandler= new TransactionHandler(); txHandler.setTarget(testUserService); txHandler.setPattern("upgradeLevels"); UserServicetxUserService = (UserService)Proxy.newProxyInstance( getClass().getClassLoader(), new Class[] {UserService.class}, txHandler); … }
다이내믹 프록시를 위한 팩토리빈 • 팩토리빈을 사용하면 다이내믹 프록시를스프링빈으로 등록할 수 있다. • FactoryBean<T>를스프링빈으로 등록해 놓으면 이것이 T 타입 오브젝트를 만들어 역시 스프링빈을 등록해 준다. org.springframework.beans.factory; Interface FactoryBean<T> { T getObject() ; Class<?> getObjectType() ; booleanisSingleton() ; }
BeanFactory사용 예 package springbook.learningtest.spring.factorybean; public class Message { String text; private Message(String text) { this.text = text; } public String getText() { return text; } public static Message newMessage(String text) { return new Message(text); } }
public class MessageFactoryBean implements FactoryBean<Message> { String text; public void setText(String text) { this.text = text; } public Message getObject() throws Exception { return Message.newMessage(this.text); } public Class<? extends Message> getObjectType() { return Message.class; } public booleanisSingleton() { return false; } }
<bean id="message" class="springbook.learningtest.spring.factorybean.MessageFactoryBean"> <property name="text" value="Factory Bean" /> </bean> @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration public class FactoryBeanTest { @Autowired ApplicationContext context; @Test public void getMessageFromFactoryBean() { Object message = context.getBean("message"); assertThat(message, is(Message.class)); assertThat(((Message)message).getText(), is("Factory Bean")); } @Test public void getFactoryBean() throws Exception { Object factory = context.getBean("&message"); assertThat(factory, is(MessageFactoryBean.class)); } }
팩토리빈을 이용하여 트랜잭션 다이내믹 프록시를스프링빈으로 등록 왜 다이나믹프록시를스프링빈으로 만들려 하나?
public class TxProxyFactoryBean implements FactoryBean<Object> { … public Object getObject() throws Exception { TransactionHandlertxHandler = new TransactionHandler(); txHandler.setTarget(target); txHandler.setTransactionManager(transactionManager); txHandler.setPattern(pattern); return Proxy.newProxyInstance( getClass().getClassLoader(), new Class[] { serviceInterface }, txHandler); } … }
<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>
다이나믹프록시를 사용하면 프록시 패턴 혹은 데커레이션 패턴이 갖는 아래 두 가지 문제 해결 • 팩토리빈을 사용하면 다이나믹프록시를스프링빈으로 등록시키고 다른 오브젝트에 DI할 수 있다. 인터페이스의 모든 메소드를 구현해야 함 똑같은 부가 기능이 모든 메소드에 중복됨
한번 만든 TxProxyFactoryBean은 다른 서비스에도 그대로 적용할 수 있다. 리스트 6-38, 6-39, 6-40 coreService라는 아이디를 가진 빈을 DI 받아 사용하는 클라이언트는 코드 변경 없이도 프록시가 제공하는 트랜잭션 기능이 적용된 coreService를 이용할 수 있다.
팩토리빈의 한계 • 여러 개의 클래스에 공통의 부가기능 지원하기 위해서는여러 개의 팩토리빈을 설정해야 함 • 하나의 타깃에 여러 가지 부가기능을 지원하기 위해서는 여러 개의 팩토리빈을 설정해야 함
6.4 스프링의 팩토리빈(ProxyFactoryBean) JDK 다이나믹프록시 FactoryBean<T> 스프링 다이나믹프록시 타깃과 연결되어 있지 않음
어드바이저= 포인트컷(메소드 선정) + 어드바이스(부가기능) 스프링 다이나믹프록시