Spring @Autowired 필드가 왜 null입니까?


609

참고 : 이것은 일반적인 문제에 대한 정식 답변입니다.

필드 ( ) 가있는 Spring @Service클래스 ( MileageFeeCalculator)가 있지만 필드는 사용하려고합니다. 로그는 Bean과 Bean이 모두 생성되고 있음을 보여 주지만 서비스 Bean 에서 메소드 를 호출하려고 할 때마다 메시지가 표시 됩니다. Spring이 필드를 자동 배선하지 않는 이유는 무엇입니까?@AutowiredrateServicenullMileageFeeCalculatorMileageRateServiceNullPointerExceptionmileageCharge

컨트롤러 클래스 :

@Controller
public class MileageFeeController {    
    @RequestMapping("/mileage/{miles}")
    @ResponseBody
    public float mileageFee(@PathVariable int miles) {
        MileageFeeCalculator calc = new MileageFeeCalculator();
        return calc.mileageCharge(miles);
    }
}

서비스 클래스 :

@Service
public class MileageFeeCalculator {

    @Autowired
    private MileageRateService rateService; // <--- should be autowired, is null

    public float mileageCharge(final int miles) {
        return (miles * rateService.ratePerMile()); // <--- throws NPE
    }
}

자동 연결되어야 MileageFeeCalculator하지만 그렇지 않은 서비스 Bean :

@Service
public class MileageRateService {
    public float ratePerMile() {
        return 0.565f;
    }
}

시도 할 GET /mileage/3때이 예외가 발생합니다.

java.lang.NullPointerException: null
    at com.chrylis.example.spring_autowired_npe.MileageFeeCalculator.mileageCharge(MileageFeeCalculator.java:13)
    at com.chrylis.example.spring_autowired_npe.MileageFeeController.mileageFee(MileageFeeController.java:14)
    ...

3
또 다른 시나리오는 bean F이 다른 bean의 생성자 내에서 호출 될 수 있습니다 S. 이 경우 필요한 Bean F을 다른 Bean S생성자에 매개 변수로 전달하고 Swith 의 생성자에 주석을 답니다 @Autowire. 첫 번째 빈의 클래스에 주석을 기억 F과를 @Component.
aliopi

github.com/swimorsink/spring-aspectj-examples에서 Gradle을 사용 하여이 예제와 매우 유사한 몇 가지 예제를 코딩했습니다 . 잘만되면 누군가 유용하게 사용될 것입니다.
Ross117

답변:


649

주석 @Autowired이 달린 필드 는 nullSpring이 MileageFeeCalculator생성 한 사본에 대해 Spring이 알지 new못하고 자동 와이어 링을 알지 못했기 때문입니다.

IoC (Spring Inversion of Control) 컨테이너 에는 3 가지 주요 논리 구성 요소 ApplicationContext가 있습니다. 컨텍스트에서 Bean과의 종속성 및 여러 다른 Bean의 구성을보고 필요한 순서로 인스턴스화하고 구성하는 방법을 결정할 수있는 종속성 솔버.

IoC 컨테이너는 마법이 아니며 Java 객체에 대해 알지 못하는 한 Java 객체를 알 수있는 방법이 없습니다. 를 호출 new하면 JVM이 새 객체의 사본을 인스턴스화하여 바로 전달합니다. 구성 프로세스를 거치지 않습니다. Bean을 구성 할 수있는 세 가지 방법이 있습니다.

이 GitHub 프로젝트 에서 Spring Boot를 사용하여이 코드를 모두 게시 했습니다 . 각 접근 방식에 대해 전체 실행 프로젝트를 살펴보고 작동하는 데 필요한 모든 것을 볼 수 있습니다. 태그 NullPointerException:nonworking

콩을 주입

가장 바람직한 옵션은 Spring이 모든 빈을 자동으로 연결하도록하는 것입니다. 이것은 가장 적은 양의 코드를 요구하며 가장 유지 보수가 쉽습니다. 원하는대로 자동 배선을 수행하려면 다음 MileageFeeCalculator과 같이 자동 배선하십시오 .

@Controller
public class MileageFeeController {

    @Autowired
    private MileageFeeCalculator calc;

    @RequestMapping("/mileage/{miles}")
    @ResponseBody
    public float mileageFee(@PathVariable int miles) {
        return calc.mileageCharge(miles);
    }
}

다른 요청에 대해 서비스 오브젝트의 새 인스턴스를 작성해야하는 경우 Spring Bean 범위를 사용하여 여전히 주입을 사용할 수 있습니다 .

@MileageFeeCalculator서비스 객체 를 주입하여 작동하는 태그 :working-inject-bean

@Configurable 사용

자동 new와이어 링으로 생성 된 객체가 실제로 필요한 경우 AspectJ 컴파일 타임 직조와 함께 Spring @Configurable주석을 사용 하여 객체를 주입 할 수 있습니다. 이 접근법은 Spring이 새 인스턴스를 구성 할 수 있도록 Spring이 작성되고 있음을 경고하는 코드를 객체의 생성자에 삽입합니다. 이를 위해서는 빌드에서 약간의 구성 (예 : 컴파일 ajc) 및 Spring의 런타임 구성 핸들러 ( @EnableSpringConfiguredJavaConfig 구문 사용)가 필요합니다. 이 접근 방식은 Roo Active Record 시스템에서 new엔티티 인스턴스가 필요한 지속성 정보를 가져올 수 있도록 사용 합니다.

@Service
@Configurable
public class MileageFeeCalculator {

    @Autowired
    private MileageRateService rateService;

    public float mileageCharge(final int miles) {
        return (miles * rateService.ratePerMile());
    }
}

@Configurable서비스 객체 를 사용하여 작동하는 태그 :working-configurable

수동 Bean 조회 : 권장하지 않음

이 방법은 특별한 상황에서 레거시 코드와의 인터페이스에만 적합합니다. Spring이 자동 와이어 링하고 레거시 코드가 호출 할 수있는 싱글 톤 어댑터 클래스를 작성하는 것이 거의 항상 바람직하지만 Spring 애플리케이션 컨텍스트에 Bean을 직접 요청할 수 있습니다.

이를 위해서는 Spring이 ApplicationContext객체에 대한 참조를 제공 할 수있는 클래스가 필요 합니다.

@Component
public class ApplicationContextHolder implements ApplicationContextAware {
    private static ApplicationContext context;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        context = applicationContext;   
    }

    public static ApplicationContext getContext() {
        return context;
    }
}

그런 다음 레거시 코드는 getContext()필요한 Bean을 호출 하고 검색 할 수 있습니다 .

@Controller
public class MileageFeeController {    
    @RequestMapping("/mileage/{miles}")
    @ResponseBody
    public float mileageFee(@PathVariable int miles) {
        MileageFeeCalculator calc = ApplicationContextHolder.getContext().getBean(MileageFeeCalculator.class);
        return calc.mileageCharge(miles);
    }
}

Spring 컨텍스트에서 서비스 오브젝트를 수동으로 조회하여 작동하는 태그 : working-manual-lookup


1
살펴볼 또 다른 것은 Bean에서 Bean에 대한 오브젝트 @Configuration를 작성하는 것입니다. 여기서 특정 Bean 클래스의 인스턴스를 작성하는 메소드에 주석이 @Bean있습니다.
Donal Fellows

@DonalFellows 나는 당신이 무엇을 말하는지 완전히 확신하지 못합니다 ( "만들기"가 모호합니다). @BeanSpring Proxy AOP 를 사용할 때 메소드 를 여러 번 호출하는 문제에 대해 이야기하고 있습니까?
chrylis-신중하게 낙관적

1
안녕하세요, 비슷한 문제가 발생하지만 첫 번째 제안을 사용할 때 "mileageFee"메서드를 호출하면 응용 프로그램에서 "calc"가 null로 간주됩니다. 마치 초기화하지 않는 것처럼 보입니다 @Autowired MileageFeeCalculator calc. 이견있는 사람?
Theo

답의 맨 위에 모든 것을 수행하는 루트 인 첫 번째 Bean을 검색하는 방법을 설명하는 항목을 추가해야한다고 생각합니다 ApplicationContext. 일부 사용자 (중복으로 폐쇄 된)는 이것을 이해하지 못합니다.
Sotirios Delimanolis

@SotiriosDelimanolis 문제를 설명하십시오; 정확히 무슨 요점을하는지 잘 모르겠습니다.
chrylis-신중하게 낙관적

59

웹 애플리케이션을 코딩하지 않는 경우 @Autowiring이 수행되는 클래스가 스프링 빈인지 확인하십시오. 일반적으로 스프링 컨테이너는 스프링 빈으로 생각할 수있는 클래스를 인식하지 못합니다. 스프링 클래스에 대해 스프링 컨테이너에 알려줘야합니다.

appln-contxt에서 구성하여 얻을 수 있거나 클래스에 @Component 로 주석을 달고 더 나은 방법 은 새 연산자를 사용하여 주석이 달린 클래스를 만들지 마십시오. 아래와 같이 Appln-context에서 가져와야합니다.

@Component
public class MyDemo {


    @Autowired
    private MyService  myService; 

    /**
     * @param args
     */
    public static void main(String[] args) {
        // TODO Auto-generated method stub
            System.out.println("test");
            ApplicationContext ctx=new ClassPathXmlApplicationContext("spring.xml");
            System.out.println("ctx>>"+ctx);

            Customer c1=null;
            MyDemo myDemo=ctx.getBean(MyDemo.class);
            System.out.println(myDemo);
            myDemo.callService(ctx);


    }

    public void callService(ApplicationContext ctx) {
        // TODO Auto-generated method stub
        System.out.println("---callService---");
        System.out.println(myService);
        myService.callMydao();

    }

}

안녕, 나는 당신의 해결책을 겪었습니다, 맞습니다. 그리고 여기서 알고 싶습니다. "왜 우리는 새로운 연산자를 사용하여 주석이 달린 클래스의 인스턴스를 만들지
Ashish

3
u를 사용하여 오브젝트를 작성하는 경우 u는 IOC의 개념과 모순되는 Bean의 수명주기를 처리합니다. 컨테이너에 더 좋은 방법으로 요청하십시오
Shirish Coolkarni

41

실제로, JVM 관리 오브젝트 또는 스프링 관리 오브젝트를 사용하여 메소드를 호출해야합니다. 컨트롤러 클래스의 위 코드에서 자동 유선 객체가있는 서비스 클래스를 호출하는 새 객체를 만들고 있습니다.

MileageFeeCalculator calc = new MileageFeeCalculator();

그렇게 작동하지 않습니다.

솔루션은이 MileageFeeCalculator를 컨트롤러 자체의 자동 유선 객체로 만듭니다.

아래와 같이 컨트롤러 클래스를 변경하십시오.

@Controller
public class MileageFeeController {

    @Autowired
    MileageFeeCalculator calc;  

    @RequestMapping("/mileage/{miles}")
    @ResponseBody
    public float mileageFee(@PathVariable int miles) {
        return calc.mileageCharge(miles);
    }
}

4
이것이 답입니다. 새로운 MilageFeeCalculator를 직접 인스턴스화하고 있기 때문에 Spring은 인스턴스화에 관여하지 않으므로 스프링 스프링은 객체가 존재한다는 것을 알지 못합니다. 따라서 의존성 주입과 같이 아무것도 할 수 없습니다.
Robert Greathouse

26

내가 익숙하지 않을 때 같은 문제가 발생했습니다 the life in the IoC world. @Autowired내 콩의 하나의 필드는 런타임에 null입니다.

근본 원인은 Spring IoC 컨테이너에 의해 유지 관리되는 자동 작성 Bean ( @Autowired필드가 indeed올바르게 주입 됨) 을 사용하는 대신 newing해당 Bean 유형의 고유 한 인스턴스입니다. 물론 @Autowired스프링은 필드를 주입 ​​할 기회가 없기 때문에이 필드는 null입니다.


22

당신의 문제는 새로운 것입니다 (자바 스타일의 객체 생성)

MileageFeeCalculator calc = new MileageFeeCalculator();

주석으로 @Service, @Component, @Configuration콩이 만들어집니다
서버가 시작될 때 스프링의 애플리케이션 컨텍스트. 그러나 new 연산자를 사용하여 객체를 만들면 이미 만들어진 응용 프로그램 컨텍스트에 객체가 등록되지 않습니다. 예를 들어 Employee.java 클래스를 사용했습니다.

이것 좀 봐:

public class ConfiguredTenantScopedBeanProcessor implements BeanFactoryPostProcessor {

@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
    String name = "tenant";
    System.out.println("Bean factory post processor is initialized"); 
    beanFactory.registerScope("employee", new Employee());

    Assert.state(beanFactory instanceof BeanDefinitionRegistry,
            "BeanFactory was not a BeanDefinitionRegistry, so CustomScope cannot be used.");
    BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;

    for (String beanName : beanFactory.getBeanDefinitionNames()) {
        BeanDefinition definition = beanFactory.getBeanDefinition(beanName);
        if (name.equals(definition.getScope())) {
            BeanDefinitionHolder proxyHolder = ScopedProxyUtils.createScopedProxy(new BeanDefinitionHolder(definition, beanName), registry, true);
            registry.registerBeanDefinition(beanName, proxyHolder.getBeanDefinition());
        }
    }
}

}

12

나는 Spring을 처음 사용하지만이 작업 솔루션을 발견했습니다. 그것이 혐오스러운 방법인지 알려주세요.

applicationContext이 빈에 Spring을 주입합니다 .

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;

@Component
public class SpringUtils {

    public static ApplicationContext ctx;

    /**
     * Make Spring inject the application context
     * and save it on a static variable,
     * so that it can be accessed from any point in the application. 
     */
    @Autowired
    private void setApplicationContext(ApplicationContext applicationContext) {
        ctx = applicationContext;       
    }
}

원하는 경우이 코드를 기본 응용 프로그램 클래스에도 넣을 수 있습니다.

다른 클래스는 다음과 같이 사용할 수 있습니다.

MyBean myBean = (MyBean)SpringUtils.ctx.getBean(MyBean.class);

이런 식으로 응용 프로그램의 모든 객체 (와 함께 new)와 정적 방식으로 Bean을 얻을 수 있습니다 .


1
이 패턴은 스프링 빈을 레거시 코드에 액세스 할 수 있도록하는 데 필요하지만 새 코드에서는 피해야합니다.
chrylis-신중하게 낙관적-5

2
당신은 Spring을 처음 사용하지 않습니다. 당신은 전문가입니다. :)
sapy

당신은 저를 구했습니다 ...
Govind Singh

제 경우에는 제 3 자 수업이 거의 없었기 때문에 이것이 필요했습니다. Spring (IOC)은 이에 대한 통제권이 없었습니다. 이 클래스는 내 봄 부팅 앱에서 호출되지 않았습니다. 나는이 접근법을 따랐고 그것은 나를 위해 일했다.
Joginder Malik

12

드문 경우이지만 여기에 나에게 일어난 일이 있습니다.

@Inject대신 @AutowiredSpring에서 지원하는 javaee 표준을 사용 했습니다 . 모든 장소는 잘 작동했고 콩은 한곳이 아닌 올바르게 주입되었습니다. 콩 주입은 같은 것 같습니다

@Inject
Calculator myCalculator

마침내 우리는 (실제로 Eclipse 자동 완성 기능) com.opensymphony.xwork2.Inject대신에 가져온 오류라는 것을 발견했습니다.javax.inject.Inject !

그래서 반드시 주석 (즉, 메이크업을 요약하면 @Autowired, @Inject, @Service, ...) 올바른 패키지가!


5

봄에 주석이있는 클래스를 스캔하도록 지시하는 것을 놓친 것 같습니다.

@ComponentScan("packageToScan")스프링 애플리케이션의 구성 클래스를 사용하여 스프링이 스캔하도록 지시 할 수 있습니다 .

@Service, @Component 기타 주석은 메타 설명을 추가합니다.

Spring은 Bean으로 작성되거나 주석으로 표시된 클래스의 인스턴스 만 삽입합니다.

주석이 표시된 클래스는 주입하기 전에 스프링으로 식별해야하며 스프링 @ComponentScan이 주석으로 표시된 클래스를 찾도록 지시하십시오. Spring은 @Autowired관련 Bean을 검색하고 필요한 인스턴스를 삽입합니다.

주석 만 추가하고 의존성 주입을 수정하거나 촉진하지는 않습니다. Spring은 어디를 찾아야하는지 알아야합니다.


파일 에 추가 <context:component-scan base-package="com.mypackage"/>하는 것을 잊었을 때이 문제가 발생했습니다beans.xml
Ralph Callaway

5

테스트 수업에서 이런 일이 발생하면 수업에 주석을 달 것을 잊지 마십시오.

예를 들어 Spring Boot에서 :

@RunWith(SpringRunner.class)
@SpringBootTest
public class MyTests {
    ....

시간이 지나면 ...

스프링 부트 는 계속 발전하고 있습니다. @RunWith 올바른 JUnit 버전을 사용하는 경우 더 이상 사용할 필요가 없습니다 .

들어 @SpringBootTest작업에 혼자 서, 당신은 사용해야 @Test에서 JUnit5 대신 JUnit4 .

//import org.junit.Test; // JUnit4
import org.junit.jupiter.api.Test; // JUnit5

@SpringBootTest
public class MyTests {
    ....

이 구성의 잘못을받을 경우 테스트를 컴파일하지만, @Autowired@Value필드 (예를 들어)입니다 null. Spring Boot는 마술로 작동하기 때문에이 오류를 직접 디버깅 할 수있는 방법이 거의 없습니다.



참고 : 필드 @Value와 함께 사용하면 null이됩니다 static.
nobar

Spring은 컴파일러의 도움없이 여러 가지 실패 방법을 제공합니다. 일이 잘못되면 가장 좋은 방법은 함께 작동하는 주석 조합 만 사용하여 정사각형으로 돌아가는 것입니다.
nobar

4

또 다른 솔루션은 다음 SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this)
과 같이 호출하는 것입니다. To MileageFeeCalculator 생성자 :

@Service
public class MileageFeeCalculator {

    @Autowired
    private MileageRateService rateService; // <--- will be autowired when constructor is called

    public MileageFeeCalculator() {
        SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this)
    }

    public float mileageCharge(final int miles) {
        return (miles * rateService.ratePerMile()); 
    }
}

안전하지 않은 게시를 사용합니다.
chrylis-주의 깊게 낙관적-4

3

업데이트 : 실제로 똑똑한 사람들은 답변 을 신속하게 지적하여 아래 설명 된 이상한 점을 설명합니다.

원래 답변 :

그것이 누군가에게 도움이되는지 모르겠지만, 겉보기에는 옳은 일을하는 동안에도 같은 문제가 발생했습니다. 내 Main 메서드에는 다음과 같은 코드가 있습니다.

ApplicationContext context =
    new ClassPathXmlApplicationContext(new String[] {
        "common.xml",
        "token.xml",
        "pep-config.xml" });
    TokenInitializer ti = context.getBean(TokenInitializer.class);

그리고 token.xml파일에는 줄이 있습니다.

<context:component-scan base-package="package.path"/>

package.path가 더 이상 존재하지 않음을 알았으므로 방금 줄을 삭제했습니다.

그리고 나서 NPE가 들어 오기 시작했습니다 pep-config.xml.

<bean id="someAbac" class="com.pep.SomeAbac" init-method="init"/>
<bean id="settings" class="com.pep.Settings"/>

SomeAbac 클래스에는 다음과 같이 선언 된 속성이 있습니다.

@Autowired private Settings settings;

알 수없는 이유로 설정이 <context:component-scan/> 요소가 전혀 존재하지 않을 때 init ()에서 null 이지만, 존재하고 basePackage로 bs가 있으면 모든 것이 잘 작동합니다. 이 줄은 이제 다음과 같습니다 :

<context:component-scan base-package="some.shit"/>

작동합니다. 누군가가 설명을 제공 할 수는 있지만 지금은 충분합니다.)


5
그 대답 은 설명입니다. <context:component-scan/>암시 적 으로 작동 <context:annotation-config/>하는 데 필요 @Autowired합니다.
ForNeVeR

3

이것은 NullPointerException을주는 원인입니다. MileageFeeCalculator calc = new MileageFeeCalculator();Spring을 사용하고 있습니다-수동으로 객체를 만들 필요가 없습니다. 객체 생성은 IoC 컨테이너에 의해 처리됩니다.


2

서비스 클래스에서 @Service 어노테이션을 사용하고 필요한 Bean classA를 다른 Bean classB 생성자에 매개 변수로 전달하고 @Autowired로 classB의 생성자에 주석을 달 수도 있습니다. 샘플 스 니펫은 다음과 같습니다.

@Service
public class ClassB {

    private ClassA classA;

    @Autowired
    public ClassB(ClassA classA) {
        this.classA = classA;
    }

    public void useClassAObjectHere(){
        classA.callMethodOnObjectA();
    }
}

이것은 나를 위해 일했습니다.이 문제를 어떻게 해결하고 있는지 자세히 설명해 주시겠습니까?
CruelEngine

1
@CruelEngine, 이것은 필드 주입 (대부분 스프링 구성에 의해 주로 수행됨) 대신 생성자 주입 (명시 적으로 객체를 설정하는 곳)입니다. 따라서 "new"연산자를 사용하여 ClassB의 객체를 생성하는 경우 다른 범위는 ClassA에 대해 표시되거나 자동 유선으로 설정되지 않습니다. 따라서 classB.useClassAObjectHere ()를 호출하는 동안 필드 인젝션을 선언하면 classA 객체가 자동 연결되지 않으므로 NPE가 발생합니다. 읽기 chrylis는 동일한 설명을 시도하고 있습니다. 그리고 이것이 필드 주입보다 생성자 주입이 권장되는 이유입니다. 이제 말이 되나요?
Abhishek

1

여기에 언급되지 않은 내용은 기사에서 "실행 명령"단락에 설명되어 있습니다.

@Component 또는 파생 된 @Service 또는 @Repository 클래스에 주석을 달아야한다는 "학습"후에 (더 많은 것이있을 것 같음) 그 안에 다른 컴포넌트를 자동 와이어 링하기 위해 이러한 다른 컴포넌트가 여전히 생성자 내부에 널 (null) 인 것을 발견했습니다. 부모 구성 요소의

@PostConstruct를 사용하면 다음을 해결할 수 있습니다.

@SpringBootApplication
public class Application {
    @Autowired MyComponent comp;
}

과:

@Component
public class MyComponent {
    @Autowired ComponentDAO dao;

    public MyComponent() {
        // dao is null here
    }

    @PostConstruct
    public void init() {
        // dao is initialized here
    }
}

1

이것은 단위 테스트의 경우에만 유효합니다.

내 서비스 클래스에 서비스 주석이 있었고 @autowired다른 구성 요소 클래스였습니다. 테스트 할 때 구성 요소 클래스가 null이되었습니다. 서비스 클래스의 경우 다음을 사용하여 객체를 만들고 있었기 때문에new

단위 테스트를 작성하는 경우을 사용하여 객체를 작성하고 있지 않은지 확인하십시오 new object(). 대신 injectMock을 사용하십시오.

이것은 내 문제를 해결했습니다. 유용한 링크 는 다음과 같습니다


0

또한 어떤 이유로 든 @Serviceas로 메소드 final를 작성하면 액세스 할 자동 유선 Bean은 항상입니다 null.


0

간단히 말해서 @Autowired필드가 두 가지 이유 가 있습니다.null

  • 수업은 봄이 아닙니다.

  • 이 분야는 독이 아닙니다.


0

질문과 완전히 관련이있는 것은 아니지만 필드 삽입이 null 인 경우에도 생성자 기반 주입은 여전히 ​​잘 작동합니다.

    private OrderingClient orderingClient;
    private Sales2Client sales2Client;
    private Settings2Client settings2Client;

    @Autowired
    public BrinkWebTool(OrderingClient orderingClient, Sales2Client sales2Client, Settings2Client settings2Client) {
        this.orderingClient = orderingClient;
        this.sales2Client = sales2Client;
        this.settings2Client = settings2Client;
    }
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.