elevne's Study Note

토비의 Spring (9: AOP(3)) 본문

Backend/Toby Spring

토비의 Spring (9: AOP(3))

elevne 2023. 6. 8. 18:23

이전까지 작업한 TransactionHandler, 다이나믹 프록시를 스프링의 DI 를 통해 사용할 수 있어야 할 것이다. 하지만 DI 의 대상이 되는 다이나믹 프록시 오브젝트는 일반적인 스프링의 빈으로는 등록할 방법이 없다. 스프링은 내부적으로 리플렉션 API 를 사용하여 빈 정의에 나오는 클래스 이름을 가지고 오브젝트를 생성한다. 하지만 다이나믹 프록시 오브젝트는 이런 식으로 프록시 오브젝트가 생성되지 않는다. (어떤 클래스를 구체적으로 사용하는지도 알 수 없다) 다이나믹 프록시를 사용하게 되면 프록시 오브젝트 클래스 정보를 미리 알아내서 스프링 빈에 정의할 방법이 없다는 뜻이다. 다이나믹 프록시는 Proxy 클래스의 newProxyInstance() 메소드를 통해서만 생성할 수 있다.

 

 

, FactoryBean 이라는 것을 사용하여 문제를 해결할 있다. 팩토리빈은 스프링을 대신하여 오브젝트의 생성로직을 담당하도록 만들어진 특별한 빈이다. FactoryBean 인터페이스는 가지 메소드로 구성되어있다. 오브젝트를 생성해 돌려주는 getObject(), 생성되는 오브젝트의 타입을 알려주는 getObjectType(), 그리고 getObject() 돌려주는 오브젝트가 항상 같은 싱글톤 오브젝트인지에 대한 여부를 알려주는 isSingleton() 으로 이루어진다. FactoryBean 인터페이스를 구현한 클래스를 스프링의 빈으로 등록하면 팩토리 빈으로 동작하게 된다. 아래와 같이 TransactionHandler 이용하여 프록시를 생성하는 팩토리 빈을 작성할 있다.

 

 

package org.example.sixthchapter;

import org.springframework.beans.factory.FactoryBean;
import org.springframework.transaction.PlatformTransactionManager;

import java.lang.reflect.Proxy;

public class TxProxyFactoryBean implements FactoryBean<Object> {

    Object target;
    PlatformTransactionManager platformTransactionManager;
    String pattern;
    Class<?> serviceInterface;

    public void setTarget(Object target) {
        this.target = target;
    }

    public void setTransactionManager(PlatformTransactionManager transactionManager) {
        this.platformTransactionManager = transactionManager;
    }

    public void setPattern(String pattern) {
        this.pattern = pattern;
    }

    public void setServiceInterface(Class<?> serviceInterface) {
        this.serviceInterface = serviceInterface;
    }

    public Object getObject() throws Exception {
        TransactionHandler txHandler = new TransactionHandler();
        txHandler.setTarget(target);
        txHandler.setTransactionManager(platformTransactionManager);
        txHandler.setPattern(pattern);
        return Proxy.newProxyInstance(
                getClass().getClassLoader(), new Class[] {serviceInterface}, txHandler
        );
    }
    public Class<?> getObjectType() {
        return serviceInterface;
    }
    public boolean isSingleton() {
        return false;
    }


}

 

 

오브젝트를 생성할 필요한 정보를 팩토리 빈의 프로퍼티로 설정해서 대신 DI 받을 있게끔 한다. 주입된 정보는 오브젝트 생성 중에 사용되는 것이다. 그리고 getObject() 메소드 내에 실제 빈으로 사용할 오브젝트를 직접 위와 같이 생성해주는 것이다. 서적에서는 계속 xml 파일에서 정의를 해주고 있지만, @Configuration 어노테이션을 사용하는 자바 파일 내에서 아래와 같이 정의를 해줄 있다.

 

 

@Bean(name = "userService")
public UserService userService() throws Exception {
    TxProxyFactoryBean factory = new TxProxyFactoryBean();
    factory.setTarget(userServiceImpl());
    factory.setPattern("upgradeLevels");
    factory.setTransactionManager(platformTransactionManager());
    factory.setServiceInterface(UserService.class);
    return (UserService) factory.getObject();
}

 

 

 

이렇게 생성해준 빈을 이용하여 테스트를 진행해볼 있다. UserSerive 라는 이름으로 등록한 빈을 @Autowire 하여 가져와서 아래 테스트를 실행해본다.

 

 

@Autowired
private UserService userService;
@Test
public void 프록시팩토리테스트() throws Exception {
    userDao.deleteAll();
    for (User user : users) userDao.add(user);
    try {
        userService.upgradeLevels();
    } catch (Exception e) {
    }
    checkLevelUpgraded(users.get(1), true);
}

 

 

result

 

 

다이나믹 프록시를 생성해주는 팩토리 빈을 사용하는 것은 여러 장점이 있다고 한다. 우선 위와 같이 Object 타입으로 지정해주면 UserSerivce 뿐만 아니라 이후에 다른 서비스 클래스들에 대해서도 위 팩토리 빈을 이용하여 프록시를 이용한 트랜잭션 처리를 해줄 수 있다. 또 앞서 알아본 데코레이터 패턴의 두 가지 문제점 (일일이 만들어야 하는 번거로움, 코드 중복 문제) 을 해결해준다. 

 

 

하지만 방법 또한 한계가 있다 한다. 프록시를 통해 타깃에 부가기능을 제공하는 것은 메소드 단위로 일어나는 일인데, 하나의 클래스 안에 존재하는 여러 개의 메소드에 부가기능을 번에 제공하는 것은 어렵지 않게 가능하지만 번에 여러 개의 클래스에 공통적인 부가기능을 제공하는 것은 지금까지의 방식으로 불가능하다. , 하나의 타깃에 여러 개의 부가기능을 적용하려고 때에도 문제 생길 있다. 같은 타깃 오브젝트에 대해 트랜잭션 뿐만 아니라 보안 기능 혹은 외의 부가기능을 담은 프록시도 추가하고자 한다면 정의가 매우 복잡해질 것이다. 더구나, 방식에서는 TransactionHandler 오브젝트가 프록시 팩토리 개수만큼 만들어진다. 이를 싱글톤 빈으로 만들어서 사용할 있다면 효율적일 것이다. 

 

 

 

 

 

 

 

Reference:

토비의 스프링

'Backend > Toby Spring' 카테고리의 다른 글

토비의 Spring (9: AOP(5))  (0) 2023.06.13
토비의 Spring (9: AOP(4))  (0) 2023.06.09
토비의 Spring (9: AOP(2))  (0) 2023.06.07
토비의 Spring (9: AOP(1))  (0) 2023.05.22
토비의 Spring (8: 서비스 추상화(2))  (0) 2023.05.18