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

java spring null nullpointerexception autowired


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

@Autowired 필드 ( rateService )가있는 Spring @Service 클래스 ( MileageFeeCalculator )가 있지만 사용하려고하면 필드가 null 입니다. 로그는 MileageFeeCalculator Bean과 MileageRateService Bean이 모두 작성되고 있음을 보여 주지만 서비스 Bean에서 mileageCharge 메소드를 호출하려고 할 때마다 NullPointerException 이 발생 합니다. Spring이 왜 필드를 자동 배선하지 않습니까?

컨트롤러 클래스 :

@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)
    ...




Answer 1 chrylis -on strike-


Spring이 new 로 작성한 MileageFeeCalculator 의 사본을 알지 못하기 때문에 @Autowired 로 주석이 달린 필드는 null 입니다.

IoC (Spring Inversion of Control) 컨테이너 에는 3 가지 주요 논리 구성 요소가 있습니다. 응용 프로그램에서 사용할 수있는 구성 요소 (beans)의 레지스트리 ( ApplicationContext 라고 함), 일치하여 오브젝트의 종속성을 주입하는 구성자 시스템 컨텍스트에서 Bean과의 종속성 및 여러 다른 Bean의 구성을보고 필요한 순서로 인스턴스화하고 구성하는 방법을 결정할 수있는 종속성 솔버.

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

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

콩을 주입

가장 바람직한 옵션은 Spring이 모든 빈을 자동으로 연결하도록하는 것입니다. 이것은 가장 적은 양의 코드를 요구하며 가장 유지 보수가 용이합니다. 원하는대로 MileageFeeCalculator 하려면 다음 과 같이 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의 런타임 구성 핸들러 (JavaConfig 구문으로 @EnableSpringConfigured ) 를 켜야 합니다. 이 접근법은 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




Answer 2 Shirish Coolkarni


웹 애플리케이션을 코딩하지 않는 경우 @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();

    }

}



Answer 3 Ravi Durairaj


실제로 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);
    }
}



Answer 4 smwikipedia


나는 the life in the IoC world 익숙하지 않을 때 같은 문제에 직면했다. 내 Bean 중 하나의 @Autowired 필드가 런타임에 널입니다.

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




Answer 5 Deepak


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

MileageFeeCalculator calc = new MileageFeeCalculator();

@Service , @Component 주석을 사용하면 @Configuration Bean이
서버가 시작될 때 Spring의 애플리케이션 컨텍스트. 그러나 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());
        }
    }
}

}