Spring @Service 호출 null - Spring @Service hochul null

안녕하세요 초이스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

Toplist

최신 우편물

태그