1. 직접 프록시 객체 작성 - 데코레이터 패턴
- 직접 작성하므로 프록시 대상 클래스마다 클래스 구현 필요..
public class UserServiceTx implements UserService{
UserService userService; // 타겟 오브젝트
PlatformTransactionManager transactionManager;
public void setTransactionManager(
PlatformTransactionManager transactionManager){
this.transactionManager = transactionManager;
}
public void setUserService (UserService userService){
this.userService = userService;
}
@Override
public void add(User user) { // 메소드 구현과 위임
userService.add(user);
}
@Override
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;
}
}
}
// 프록시 대상 객체마다 클래스를 새로 구현해야함
@Bean
public UserServiceTx userService (){
UserServiceTx userServiceTx = new UserServiceTx();
userServiceTx.setUserService(userServicImpl());
serServiceTx.setTransactionManager(transactionManager());
}
2. 타겟을 사용하는 InvocationHandler사용
- 타겟마다 팩토리빈 구현 및 빈 설정을 해주어야함.
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;
}
@Override
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.transactionManager.rollback(status);//부가기능
throw e.getTargetException();
}
}
}
public class TxProxyFactoryBean implements FactoryBean<Object> { //생성할 오브젝트 타입을 지정할 수도 있지만 범용적으로 하기 위해 Object로
Object target;
PlatformTransactionManager transactionManager; //TransactionHandler를 생성할 때 필요
String pattern;
Class<?> serviceInterface; //다이나믹 프록시를 생성할 때 필요. UserService외의 인터페이스를 가진 타깃에도 적용.
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 인터페이스 구현 메소드
@Override
public Object getObject() throws Exception { //DI 받은 정보를 이용해서 TransactionHandler를 사용하는 다이나믹 프록시를 생성
TransactionHandler txHanler = new TransactionHandler(); //싱글톤이 아님.. 타겟 설정 필요..
txHanler.setTarget(target);
txHanler.setTransactionManager(transactionManager);
txHanler.setPattern(pattern);
return Proxy.newProxyInstance(
getClass().getClassLoader(),
new Class[]{serviceInterface},
txHanler);
}
@Override
public Class<?> getObjectType() { //DI 받은 인터페이스 타입에 따라 달라짐
return serviceInterface;
}
@Override
public boolean isSingleton() { //싱글톤 빈이 아니라는 뜻이 아니라 getObject()가 매번 같은 오브젝트를 리턴하지 않는다는 의미!
return false;
}
}
// 빈설정 --> 타겟마다 팩토리빈을 구현해주어야하는 중복 발생
@Bean
public TxProxyFactoryBean userService(){
TxProxyFactoryBean txProxyFactoryBean = new TxProxyFactoryBean();
txProxyFactoryBean.setTarget(userServicImpl()); //핵심기능->핸들러의invoke()메소드에서 사용하기 위함
txProxyFactoryBean.setTransactionManager(transactionManager()); //부가기능
txProxyFactoryBean.setPattern("upgradeLevels"); // 메소드선정
txProxyFactoryBean.setServiceInterface(UserService.class); //Class타입인 경우
return txProxyFactoryBean;
}
3. 타겟 직접 사용하지 않는 MethodInvocation 사용 (템플릿/콜백 패턴)
- 빈 설정 시에만 타겟 지정할 수 있게됨.
- 하지만 타겟이 많으면 빈 설정도 너무 많아짐.
public class TransactionAdvice implements MethodInterceptor {
PlatformTransactionManager transactionManager;
public void setTransactionManager(PlaformTransactionManager transactionManager){
this.transactionManager = transactionManager;
}
// 타깃을 호출하는 기능을 가진 콜백오브젝트(MethodInvocation)을 프록시로부터 받음
// 덕분에 어드바이스는 특정 타깃에 의존하지 않고 재사용 가능 -> 빈 등록 가능
@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;
} catch (RuntimeException e){
this.transactionManager.rollback(status);
throw e;
}
}
}
//빈 설정 클래스
@Bean //부가기능(어드바이스)
public TransactionAdvice transactionAdvice(){
TransactionAdvice transactionAdvice = new TransactionAdvice(); // MethodInterceptor을 구현하여 어드바이스로 생성
transactionAdvice.setTransactionManager(transactionManager());
return transactionAdvice;
}
@Bean // 어드바이스와 포인트컷을 담을 어드바이저 등록
public DefaultPointcutAdvisor transactionAdvisor(){
DefaultPointcutAdvisor defaultPointcutAdvisor = new DefaultPointcutAdvisor();
defaultPointcutAdvisor.setAdvice(transactionAdvice());
defaultPointcutAdvisor.setPointcut(transactionPointcut());
return defaultPointcutAdvisor;
}
//기존 팩토리 빈 -> 프록시 팩토리 빈으로 교체하여 타겟과 부가기능만 설정할 수 있도록 변경됨
@Bean // 타겟과 어드바이저를 담을 프록시 팩토리 빈 등록
public ProxyFactoryBean userService(){
ProxyFactoryBean proxyFactoryBean = new ProxyFactoryBean();
proxyFactoryBean.setTarget(userServicImpl());
proxyFactoryBean.setInterceptorNames("transactionAdvisor"); // 어드바이스와 어드바이저 동시 가능 설정. 리스트에 빈 아이디값 넣어줌.
return proxyFactoryBean;
}
4. 스프링이 제공하는 트랜잭션 경계설정 어드바이스 & 프록시 자동생성 빈 후처리기 사용
- TransactionInterceptor사용하여 트랜잭션 설정만 작성 가능
- 프록시 자동생성 빈 후처리기 사용하여 프록시 팩토리빈으로 일일히 타겟 지정하지 않아도 됨 (포인트컷으로 자동으로 프록시 생성)
- 스프링이 제공하는 트랜잭션 어드바이스 사용
// 스프링에서 제공하는 트랜잭션 경계설정 어드바이스로 대체
@Bean
public TransactionInterceptor transactionAdvice(){
TransactionInterceptor transactionInterceptor = new TransactionInterceptor();
transactionInterceptor.setTransactionManager(transactionManager());
Properties transactionAttributes = new Properties();
// get* 메소드에 대한 설정
transactionAttributes.setProperty("get*", "PROPAGATION_REQUIRED, readOnly");
// 나머지 메소드에 대한 기본 설정
transactionAttributes.setProperty("*", "PROPAGATION_REQUIRED");
transactionInterceptor.setTransactionAttributes(transactionAttributes);
return transactionInterceptor;
}
// 프록시 자동생성으로, 프록시 팩토리 빈설정 제거
@Bean
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
return new DefaultAdvisorAutoProxyCreator(); // 자동 프록시 생성 빈 후처리기
}
5. @Tranasactional 어노테이션 사용
- 어노테이션 부착된 메소드, 클래스에 대해 포인트컷에 추가되어 자동 프록시 생성 빈 후처리기에 의해 자동 생성됨
출저 : 토비의 스프링
'백엔드 > Spring' 카테고리의 다른 글
XSS 방지 삽질기 - 1. HTTP Method & Content-Type (0) | 2024.08.29 |
---|---|
@ResponseBody VS ResponseEntity 클래스 VS 커스텀 응답 클래스 (2) | 2024.06.25 |
WAS 내부 요청 처리를 코드로 알아보자! - 1. EndPoint 분석 (0) | 2023.12.24 |
WAS(톰캣)의 내부 구성 및 작동 과정 - 1. 초기화 단계 (0) | 2023.12.10 |