[Spring] - ApplicationContextAware (DI가 안 되는 경우 해결)

Kim Dae Hyun·2021년 8월 29일
0

Spring-JPA

목록 보기
4/4

🔎 문제배경

EntityListeners로 등록한 클래스에서 Repository를 주입받아 사용하고자 하는데 주입이 정상적으로 되지 않았습니다.

@EntityListeners(value = UserHistoryBackupListener.class)

위처럼 UserHistoryBackupListenerEntityListener로 등록했고 Listener 클래스는 아래처럼 구현했습니다.

@Slf4j
@Component
@RequiredArgsConstructor
public class UserHistoryBackupListener {

    private final UserHistoryRepository userHistoryRepository;

    @PostPersist
    @PostUpdate
    public void preUpdate(Object obj) {
        log.info("UserHistoryBackupListener preUpdate 호출");

        User user = (User) obj;

        UserHistory userHistory = UserHistory.builder()
                .userId(user.getId())
                .name(user.getName())
                .email(user.getEmail())
                .gender(user.getGender())
                .build();

        userHistoryRepository.save(userHistory);
    }
}

특별한거 없이 UserHistoryRepository를 주입받아 사용하고 있습니다.

EntityListener 클래스가 아니라면 문제가 없어야 하지만 앞서 말했듯 EntityListeners로 등록된 클래스의 경우 Spring IOC의 관리 대상이 아니므로 의존성을 땡겨올 저장소에 빈이 없을 수 있습니다.

IDE에서 에러메시지도 보여주네요.


🔎 해결

찾아본 결과 EntityListeners에 등록되는 클래스 역시 @Component로 빈으로 등록되는데 여기서 EntityListeners에 등록되는 클래스가 주입되는 빈보다 먼저 등록될 수도 있기 때문에 발생하는 에러라고 합니다.

그렇다면 해결방법 중 하나는 빈의 생성시점과 무관하게 모든 빈이 생성된 후 EntityListners에 등록된 클래스에서 사용하고자 하는 Bean을 직접 조회해서 가져온 후 사용하면 되는 것 입니다.

그럼 직접 Bean을 조회하고 받아올 수 있는 유틸 클래스를 구현합니다.

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

@Component
public class BeanUtils implements ApplicationContextAware {

    private static ApplicationContext ac;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        BeanUtils.ac = applicationContext;
    }

    public static <T> T getBean(Class<T> clazz) {
        return ac.getBean(clazz);
    }
}

ApplicationContextAware 인터페이스를 구현하여 ApplicationContext를 받아옵니다.

ApplicationCOntextAware인터페이스의 구현 메서드는 setApplicationContext 하나입니다. ApplicationContext를 받아오는 역할만을 수행하는 것 입니다.

ApplicationContext를 받아왔다면 이를 통해 클래스 타입으로 빈을 조회합니다.

제네릭을 사용하여 여러 곳에서 사용 가능한 유틸메서드로 만들었습니다.


🔎 EntityListener 클래스 변경

@Slf4j
@Component
@RequiredArgsConstructor
public class UserHistoryBackupListener {

    @PostPersist
    @PostUpdate
    public void preUpdate(Object obj) {
        log.info("UserHistoryBackupListener preUpdate 호출");
        UserHistoryRepository userHistoryRepository = BeanUtils.getBean(UserHistoryRepository.class);

        User user = (User) obj;

        UserHistory userHistory = UserHistory.builder()
                .userId(user.getId())
                .name(user.getName())
                .email(user.getEmail())
                .gender(user.getGender())
                .build();

        userHistoryRepository.save(userHistory);
    }
}

기존에 생성자 주입을 통해 Repository를 주입받는 부분을 지우고 BeanUtilsgetBean 메서드를 이용해서 UserHistoryRepository를 직접 조회해서 Bean을 받아오는 방법으로 문제가 해결되었습니다.

감사합니다 😁

profile
좀 더 천천히 까먹기 위해 기록합니다. 🧐

0개의 댓글