• 카테고리

    질문 & 답변
  • 세부 분야

    백엔드

  • 해결 여부

    미해결

자동 프록시 생성용 빈 후처리기의 반환된 프록시 빈 타입

23.03.30 21:26 작성 23.03.30 22:49 수정 조회수 335

0

토비의 스프링을 참고로 스프링 AOP를 공부하는 중 막히는 부분이 있어서 질문 남깁니다.

현재 DefaultAdvisorAutoProxyCreator라는 자동 프록시 생성기를 스프링 빈으로 등록하고, 포인트컷과 트랜잭션 부가기능을 적용할 어드바이스를 조합한 Advisor를 통해 트랜잭션이 적용되는지 테스트를 하고 있습니다. 빈 후처리기를 이용한 프록시 자동생성 방식에서 빈 후처리기는 프록시 적용 대상에 대해 프록시를 생성하고, 생성된 프록시 오브젝트를 컨테이너에게 돌려준다고 알고 있습니다. 현재 프록시 자동생성기를 이용해 예외상황에서 트랜잭션 롤백이 되는지 테스트를 돌려보았고, 여기까지 잘 통과했습니다. 그리고 나서 컨테이너가 돌려준 서비스 빈의 타입을 확인하는 과정에서 질문이 있어 남깁니다.

토비의 스프링 책에서 확인해보니 자동 프록시 생성기에 의해 프록시 오브젝트가 생성되어 돌려주기 때문에 해당 빈의 타입이 java.lang.reflect.Proxy.class이어야 한다고 했습니다. 그래서 다음과 같이 테스트를 진행했더니 오류가 발생했습니다.

혹시 프록시 생성기를 통해 돌려줘야 하는 빈의 타입이 Proxy.class 타입이 맞는 것인지, 제 코드에 문제가 있어서 오류가 발생한 것인지 알고 싶습니다. 트랜잭션 롤백 테스트를 통과한 것을 보면 프록시가 잘 생성되어 어드바이저와 연결된 것 같은데, 어째서 타입이 다르다고 나오는지 알고 싶습니다.

#추가

스프링의 AOP 프록시를 찾다보니, 프록시를 만드는 방법이 JDK와 CGLIB 두 방식이 있는 것 같습니다. ProxyFactoryBean의 Proxy.newInstance 방식으로 만든 프록시가 아닌 경우 프록시가 CGLIB 방식으로 만들어지는 것인지 궁금합니다. 빈 후처리기가 내장된 프록시 생성기를 통해 프록시를 생성한다고 했는데, DefaultAdvisorAutoProxyCreator 후처리기는 CGLIB 방식을 사용하는 것일까요? 질문이 다소 두서없지만,, 답변 기다리겠습니다. 항상 감사합니다 :)

##추추가

JDK 다이내믹 프록시를 이용한 트랜잭션 테스트에 대해서 프록시 빈 타입을 확인해보니 java.lang.reflect.Proxy.class와 동일한 것을 확인했습니다!!! DefaultAdvisorAutoProxyCreator 후처리기가 반환하는 프록시는 CGLIB proxy가 맞는 걸 보아하니 스프링은 JDK 다이내믹 프록시가 아닌 프록시 생성은 CGLIB가 디폴트일까요?

@Test
public void advisorAutoProxyCreator() {
   assertThat(testUserService).isInstanceOf(java.lang.reflect.Proxy.class);
}

제 질문만으로 이해가 부족할 것 같아서 테스트 관련한 코드들을 같이 올립니다.

@Configuration
public class BeanPostProcessorConfig {

    @Bean
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
        return new DefaultAdvisorAutoProxyCreator();
    }
}
@Configuration
public class TxAdvisorConfig {

    private final TransactionAdvice advice;
    private final UserService userService;

    @Autowired
    public TxAdvisorConfig(TransactionAdvice advice, @Qualifier("userServiceImpl") UserService userService) {
        this.advice = advice;
        this.userService = userService;
    }

    @Bean
    public NameMatchClassMethodPointcut pointcut() {
        NameMatchClassMethodPointcut pointcut = new NameMatchClassMethodPointcut();
        pointcut.setMappedClassName("*ServiceImpl");
        pointcut.setMappedName("upgrade*");
        return pointcut;
    }

    @Bean
    public DefaultPointcutAdvisor advisor() {
        return new DefaultPointcutAdvisor(pointcut(), this.advice);
    }
}
@Component
public class TransactionAdvice implements MethodInterceptor {

    private PlatformTransactionManager transactionManager;

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

    /**
     * 타깃을 호출하는 기능을 가진 콜백 오브젝트를 프록시로부터 받는다.
     * 덕분에 어드바이스는 특정 타깃에 의존하지 않고 재사용 가능하다.
     */

    @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;
        }
    }
}
@Service
public class UserServiceImpl implements UserService {

    public static final int MIN_LOGCOUNT_FOR_SILVER = 50;
    public static final int MIN_RECOMMEND_FOR_GOLD = 30;

    private final UserDao userDao;
    private UserLevelUpgradePolicy policy;

    @Autowired
    public UserServiceImpl(UserDao userDao) {
        this.userDao = userDao;
    }

    @Autowired
    public void setPolicy(UserLevelUpgradePolicy policy) {
        this.policy = policy;
    }

    public void add(User user) {
        if (user.getLevel() == null) {
            user.setLevel(Level.BASIC);
        }
        userDao.add(user);
    }

    public void upgradeLevels() {
        List<User> users = userDao.getAll();
        for (User user : users) {
            if (policy.canUpgradeLevel(user)) {
                policy.upgradeLevel(user);
            }
        }
    }
}
@Autowired
@Qualifier("testUserService")
private UserService testUserService;

@Autowired
private UserDao userDao;

@Component
@Primary
@Qualifier("testPolicy")
static class TestUserLevelPolicy extends UserLevelUpgradePolicyImpl {

   private String id = "madDitto";

   @Autowired
   private TestUserLevelPolicy(UserDao userDao, EmailPolicy emailPolicy) {
        super(userDao, emailPolicy);
   }

   public void upgradeLevel(User user) {
        if (user.getId().equals(this.id)) throw new TestUserPolicyException();
        super.upgradeLevel(user);
   }
}

@Component
@Qualifier("testUserService")
static class TestUserServiceImpl extends UserServiceImpl {

    private UserLevelUpgradePolicy testPolicy;

    @Autowired
    public TestUserServiceImpl(UserDao userDao, @Qualifier("testPolicy") UserLevelUpgradePolicy testPolicy) {
        super(userDao);
        this.testPolicy = testPolicy;
        super.setPolicy(this.testPolicy);
    }
}

@Test
@DisplayName("자동 프록시 생성 테스트")
public void upgradeAllOrNothingAutoProxy() {
    userDao.deleteAll();
    for (User user : users) {
        userDao.add(user);
    }

    try {
        this.testUserService.upgradeLevels();
        fail("TestUserPolicyException expected");
    } catch (TestUserPolicyException e) { }

    checkLevelUpgraded(users.get(1), false);
}

답변 1

답변을 작성해보세요.

1

안녕하세요. 긕긕님^^

아직 스프링 핵심 원리 - 고급편을 듣지 않으셨군요^^;

궁금해 하시는 내용들을 이번 강의(스프링 핵심 원리 - 고급편)에서 자세하게 설명해 드립니다.

강의 내용을 보시고 궁금한 내용이 있으면 또 질문 남겨주세요^^

감사합니다.