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 어노테이션 사용

- 어노테이션 부착된 메소드, 클래스에 대해 포인트컷에 추가되어 자동 프록시 생성 빈 후처리기에 의해 자동 생성됨

 

 

 

 

출저 : 토비의 스프링