@Autowired 가 안될 때 - @Autowired ga andoel ttae

우리는 보통 bean에 의존성 주입을 하고 싶을 때, @Autowired 를 사용하는데요.

@Autowired한 class 가 null 이 나왔다는 건, 해당 클래스에 의존성 주입이 제대로 이루어지지 않았다는 것입니다.

Spring에서 의존관계 주입방법은 크게 4가지가 있는데요.

1. 생성자 주입

2. setter 주입

3. 필드 주입

4. 일반 메서드 주입

이 중, 저는 필드 주입 방법으로 의존성 주입을 해줬는데요. 

(참고: Spring에서 권장하는 방식은 생성자 주입 방식 입니다)


저의 경우는, @Autowired도 잘 붙여줬고 @Service도 잘 붙여줘서 문제가 없다고 생각했는데요.

(현재 상태가 ItunesCrawlingService 클래스에 SingerDAO를 주입시켜 주어서 이 둘의 의존관계를 형성해준 것입니다.)

@Autowired 가 안될 때 - @Autowired ga andoel ttae

※ 참고

@Service, @Repository, @Component 를 붙이면 Spring Container가 관리하는 bean 객체라는 뜻입니다.

@Autowired 를 붙여주어야 두 클래스의 의존관계가 형성됩니다.


네, 여기까지는 아무 문제가 없습니다.

문제는 이 클래스를 "호출하고 있는 클래스" 로 가보세요.

(저의 경우는 ItunesCrawlingService 를 호출하고 있는 클래스로 가보았습니다.)

혹시 객체를 new로 생성했나요? 그럼 그 부분이 문제의 부분입니다.

@Autowired 가 안될 때 - @Autowired ga andoel ttae

왜냐하면

ItunesCrawlingService는 @Service 를 붙여주어 bean으로 등록된 상태입니다.

이 말은 Spring Container가 관리하고 있는 객체라는 뜻인데  이런 객체를 굳이 new로 자기가 임의로 만들어서 쓰고 있었다는 것입니다.

new로 객체를 생성해서 쓰면 그 객체는 Spring Container가 관리하는게 아니어서 @Autowired를 통한 의존성 주입이 불가해집니다.


해결방법

new로 객체를 생성하는게 아닌 @Autowired를 통해서 객체를  주입시켜 주세요.

그리고 그 주입된 객체를 가져다 쓰세요.

@Autowired 가 안될 때 - @Autowired ga andoel ttae

안녕하세요 초이스40입니다.

스프링 프레임워크를 이용해서 개발을 진행할 때 아래와 같이 null 오류가 발생할 때가 있습니다.

[2021-11-04 17:22:21.664][ERROR][kr.co.interfaces.module.cj.gisticsService:-1] - response   >CJLogistics(RESULT_CD=null, RESULT_DETAIL=Success, DATA=CJLogistics.DATA(CLSFCD=XXXX, SUBCD=0, CLSFADDR=113동 111호, CLLDANNM=수벽, CLLDLM=김**, CLLDLVEMPN=G, RSP=7, P2=null)) 
[2021-11-04 17:22:21.666][DEBUG][kr.co.web.customer.repository.master.CmOnlineMemberPolicyDao.selectOnlineMemberPolicySeq:159] - ==>  Preparing: /*+ kr.co.web.customer.repository.master.CmOnlineMemberPolicyDao.selectOnlineMemberPolicySeq [현재 시행중인 회원의 정책 seq] */ SELECT TOP 1 A.PLCY_SEQ FROM CM_ONLINE_MEMBER_POLICY A with (nolock) WHERE A.PLCY_APPLY_YMD <= GETDATE() ORDER BY A.PLCY_SEQ DESC  
[2021-11-04 17:22:21.666][DEBUG][kr.co.web.customer.repository.master.CmOnlineMemberPolicyDao.selectOnlineMemberPolicySeq:159] - ==> Parameters:  

스프링을 이용해서 개발한 경우 @Autowired 로 의존성 주입을 하게되는데 Service 클래스의 매소드를 호출할 때 해당 클래스를 new를 이용해서 생성해주게 되면 의존성 주입을 통해서 생성된 클래스를 인식하지 못한다.

그런 경우 위와 같이 해당 클래스/변수 등이 null 오류가 발생하게 된다.

@Autowired를 이용해서 의존성 주입을 한 클래스는 new를 이용해서 참조하면 안되고 일관되게 @Autowired를 이용해서 클래스를 사용해야 한다.

위와 같은 원인이 아닌데 변수의 값이 null인 경우는 해당 클래스의 메소드가 private이고 패키지 경로가 다른 경우도 해당 클래스의 변수를 참고하려할 때 null을 리턴하는 경우가 있을 수 있다.

평소에는 복붙해서 사용하다가 이런 오류가 발생하면 어디가 잘못인지 몰라 몇 시간 헤매는 경우가 발생하는데 차근차근 즐거운 개발 하시기 바랍니다.

그럼 이만.

감사합니다.

@Autowired 한 class가 null로 나오는 문제

등록된 Bean을 가져다가 사용하고 싶을 때 @Autowired로 편하게 사용하고는 한다. WebSocketConfig 에서 endpoint에 HandshakeInterceptor를 추가해 endpoint 접근하는 url의 query parameter에서 토큰을 캐치해 검증하려 했다. 이 검증을 위해 redis repository에 접근해 저장된 token이 있는지 확인해야 하는데 redis template Bean이 제대로 불러와지지 않는 문제가 있었다.

수정 전 WebSocketConfig

@RequiredArgsConstructor
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
    @Override
    public void configureMessageBroker(MessageBrokerRegistry config) {
        config.enableSimpleBroker("/topic");
        config.setApplicationDestinationPrefixes("/pub");
    }

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/wscn").addInterceptors(new HandshakeInterceptor()).setAllowedOrigins("*").withSockJS();
    }

    @Override
    public void configureClientInboundChannel(ChannelRegistration registration) {
        registration.interceptors(new FilterChannelInterceptor());
    }

}  

HandshakeInterceptor

@Component
public final class HandshakeInterceptor implements org.springframework.web.socket.server.HandshakeInterceptor {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Override
    public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler,
                                   Map<String, Object> attributes) throws Exception {

        System.out.println("before handshake: " + stringRedisTemplate);
        return true;
    }
}

StringRedisTemplate은 Redis를 제대로 설정했다면 별도 설정 없이 자동으로 Bean으로 등록되어 있음에도 불구하고 프린트하면 stringRedisTemplate가 null로 나오는 것이었다.

문제는 WebSocketConfig에서 addInterceptors(new HandshakeInterceptor()) 와 같이 의존성이 주입된 HandshakeInterceptor객체를 내 마음대로 new로 새로 만들어버렸다는 것이다. 결국 HandShakeInterceptor@Component 로 Bean으로 잘 등록해놓고는 그 Bean을 쓰지 않고 새로운 객체를 만들어버리고는 그걸 사용한 것이다. 다른 예시에서 new로 가져와 인터셉터를 등록하는 걸 보고 작성한 코드였는데 db 연결을 하기 위해 수정하는 과정에서 이 객체를 불러온 과정은 잊고 객체 안에서만 수정하려고 하고 있었다.

수정 후 WebSocketConfig

@RequiredArgsConstructor
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

/*
    @Autowired
    HandshakeInterceptor handshakeInterceptor;
*/

    @Bean
    public HandshakeInterceptor handshakeInterceptor() {
        return new HandshakeInterceptor();
    }


    @Override
    public void configureMessageBroker(MessageBrokerRegistry config) {
        config.enableSimpleBroker("/topic");
        config.setApplicationDestinationPrefixes("/pub");
    }

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/wscn").addInterceptors(handshakeInterceptor).setAllowedOrigins("*").withSockJS();
    }
} 
  1. HandshakeInterceptor@Component를 이용해 Bean으로 등록한 뒤 config에서 @Autowired 해서 사용한다.
  2. 인터셉터 객체에 @Component 를 붙이지 않고 config파일에서 인터셉터 객체를 불러와 생성하면서 Bean으로 직접 등록해서 사용한다.

스프링에서 권장하는 방법은 2번으로, 1번은 흔히 말하는 필드 주입이고 2번은 생성자 주입이다. 맨날 봐도 뭔말인지 잘 이해가 안됐는데 직접 문제를 겪고 나니 이해가 된다. 관련 좋은 글 많으니 헷갈릴때마다 찾아보기

참고자료

DI(의존성 주입)가 필요한 이유와 Spring에서 Field Injection보다 Constructor Injection이 권장되는 이유 | Mimul Tech log

Cannot Autowire Service in HandlerInterceptorAdapter