왜? 다이내믹 프록시 방식으로 변경하는가?
→ 트랜잭션이 필요한 클래스와 메소드가 증가하면 프록시 클래스를 일일이 구현하는 것이 부담됨
// UserServiceTx를 다이내믹 프록시 방식으로 변경
public class TransactionHandler implements InvocationHandler {
// 부가기능을 제공할 타깃 오브젝트. 어떤 타입의 오브젝트에도 적용 가능
private Object target;
// 트랜잭션 기능을 제공하는 데 필요한 트랜잭션 매니저
private PlatformTransactionManager transactionManager;
// 트랜잭션을 적용할 메소드 이름 패턴
private 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.transcationManager.rollback(status);
throw e.getTargetException();
}
}
}
// 이후 트랜잭션 핸들러가 필요한 정보와 오브젝트를 DI하도록
// (TransactionHandler를 이용하는 다이내믹 프록시를 사용하도록)
// 테스트 수정
DI의 대상이 되는 다이내믹 프록시 오브젝트는 일반적인 스프링의 빈으로는 등록할 방법이 없음
⇒ 팩토리 빈을 통한 생성
// FactoryBean 인터페이스
package org.spring.framework.beans.factory;
public interfave FactoryBean<T> {
// 빈 오브젝트를 생성해서 돌려줌
T getObject() throws Exception;
// 생성되는 오브젝트의 타입을 알려줌
Class<? extends T> getObjectType();
// getObject()가 돌려주는 오브젝트가 항상 같은 싱글톤 오브젝트인지 알려줌
boolean isSingleton();
// Message 클래스의 오브젝트를 생성해주는 팩토리 빈 클래스
public class MessageFactoryBean implements FactoryBean<Message> {
String text;
// 오브젝트 생성 시 필요한 정보를 팩토리 빈의 프로퍼티로 설정
// 대신 DI받을 수 있게 함
// 주입된 정보는 오브젝트 생성 중에 사용
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;
}
// getObject() 메소드가 돌려주는 오브젝트가 싱글톤인지 알려줌
// 이 팩토리 빈은 매번 요청할 때마다 새로운 오브젝트를 만들므로 false로 설정
// 팩토리 빈의 동작방식에 관한 설정
// 만들어진 빈 오브젝트는 싱글톤으로 스프링이 관리해줄 수 있다
public boolean isSingleton() {
return false;
}
}
팩토리 빈의 차이:
message 빈 오브젝트의 타입이 class애트리뷰트에 정의된 MessageFactoryBean이 아니라
Message 타입이다.
// 다이내믹 프록시를 생성하는 팩토리 빈
// 트랜잭션 프록시 빈
// 생성할 오브젝트 타입을 지정할 수도 있지만
// 범용적 사용을 위해 Object로 함
public class TxProxyFactoryBean implements FactoryBean<Object> {
// TransactionHandler를 생성할 때 필요
Object target;
PlatformTransactionManager transactionManger;
String pattern;
// 다이내믹 프록시를 생성할 때 필요
// UserService 외의 인터페이스를 가진 타깃에도 적용 가능
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 인터페이스 구현 메소드
// DI 받은 정보를 이용해서 TransactionHandler를 사용하는 다이내믹 프록시 생성
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);
}
// DI받은 정보를 이용해서 TransactionHandler를 사용하는 다이내믹 프록시 생성
public Class<?> getObjectType() {
return serviceInterface;
}
// 싱글톤 빈이 아니라는 뜻이 아니라,
// getObject()가 매번 같은 오브젝트를 리턴하지 않는다는 의미
public boolean isSingleton() {
return false;
}
}